Completed
Push — master ( 6b2386...2bf093 )
by Ahmad
04:44
created

Freemius::override_plugin_menu_with_activation()   D

Complexity

Conditions 15
Paths 90

Size

Total Lines 83
Code Lines 47

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 83
rs 4.9121
cc 15
eloc 47
nc 90
nop 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

Loading history...
2
	/**
3
	 * @package     Freemius
4
	 * @copyright   Copyright (c) 2015, Freemius, Inc.
5
	 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
6
	 * @since       1.0.3
7
	 */
8
	if ( ! defined( 'ABSPATH' ) ) {
9
		exit;
10
	}
11
12
	// "final class" only supported since PHP 5.
13
	class Freemius extends Freemius_Abstract {
14
		/**
15
		 * SDK Version
16
		 *
17
		 * @var string
18
		 */
19
		public $version = '1.1.3';
20
21
		#region Plugin Info
22
23
		/**
24
		 * @since 1.0.1
25
		 *
26
		 * @var string
27
		 */
28
		private $_slug;
29
30
		/**
31
		 * @since 1.0.0
32
		 *
33
		 * @var string
34
		 */
35
		private $_plugin_basename;
36
		/**
37
		 * @since 1.0.0
38
		 *
39
		 * @var string
40
		 */
41
		private $_free_plugin_basename;
42
		/**
43
		 * @since 1.0.0
44
		 *
45
		 * @var string
46
		 */
47
		private $_plugin_dir_path;
48
		/**
49
		 * @since 1.0.0
50
		 *
51
		 * @var string
52
		 */
53
		private $_plugin_dir_name;
54
		/**
55
		 * @since 1.0.0
56
		 *
57
		 * @var string
58
		 */
59
		private $_plugin_main_file_path;
60
		/**
61
		 * @var string[]
62
		 */
63
		private $_plugin_data;
64
		/**
65
		 * @since 1.0.9
66
		 *
67
		 * @var string
68
		 */
69
		private $_plugin_name;
70
71
		#endregion Plugin Info
72
73
		/**
74
		 * @since 1.0.9
75
		 *
76
		 * @var bool If false, don't turn Freemius on.
77
		 */
78
		private $_is_on;
79
80
		/**
81
		 * @since 1.1.3
82
		 *
83
		 * @var bool If false, don't turn Freemius on.
84
		 */
85
		private $_is_anonymous;
86
87
		/**
88
		 * @since 1.0.9
89
		 * @var bool If false, issues with connectivity to Freemius API.
90
		 */
91
		private $_has_api_connection;
92
93
		/**
94
		 * @since 1.0.9
95
		 * @var bool Hints the SDK if plugin can support anonymous mode (if skip connect is visible).
96
		 */
97
		private $_enable_anonymous;
98
99
		/**
100
		 * @since 1.0.8
101
		 * @var bool Hints the SDK if the plugin has any paid plans.
102
		 */
103
		private $_has_paid_plans;
104
105
		/**
106
		 * @since 1.0.7
107
		 * @var bool Hints the SDK if the plugin is WordPress.org compliant.
108
		 */
109
		private $_is_org_compliant;
110
111
		/**
112
		 * @since 1.0.7
113
		 * @var bool Hints the SDK if the plugin is has add-ons.
114
		 */
115
		private $_has_addons;
116
117
		/**
118
		 * @var FS_Key_Value_Storage
119
		 */
120
		private $_storage;
121
122
		/**
123
		 * @since 1.0.0
124
		 *
125
		 * @var FS_Logger
126
		 */
127
		private $_logger;
128
		/**
129
		 * @since 1.0.4
130
		 *
131
		 * @var FS_Plugin
132
		 */
133
		private $_plugin = false;
134
		/**
135
		 * @since 1.0.4
136
		 *
137
		 * @var FS_Plugin
138
		 */
139
		private $_parent_plugin = false;
140
		/**
141
		 * @since 1.1.1
142
		 *
143
		 * @var Freemius
144
		 */
145
		private $_parent = false;
146
		/**
147
		 * @since 1.0.1
148
		 *
149
		 * @var FS_User
150
		 */
151
		private $_user = false;
152
		/**
153
		 * @since 1.0.1
154
		 *
155
		 * @var FS_Site
156
		 */
157
		private $_site = false;
158
		/**
159
		 * @since 1.0.1
160
		 *
161
		 * @var FS_Plugin_License
162
		 */
163
		private $_license;
164
		/**
165
		 * @since 1.0.2
166
		 *
167
		 * @var FS_Plugin_Plan[]
168
		 */
169
		private $_plans = false;
170
		/**
171
		 * @var FS_Plugin_License[]
172
		 * @since 1.0.5
173
		 */
174
		private $_licenses = false;
175
176
		/**
177
		 * @since 1.0.1
178
		 *
179
		 * @var FS_Admin_Menu_Manager
180
		 */
181
		private $_menu;
182
183
		/**
184
		 * @var FS_Admin_Notice_Manager
185
		 */
186
		private $_admin_notices;
187
188
		/**
189
		 * @var FS_Logger
190
		 * @since 1.0.0
191
		 */
192
		private static $_static_logger;
193
194
		/**
195
		 * @var FS_Option_Manager
196
		 * @since 1.0.2
197
		 */
198
		private static $_accounts;
199
200
		/**
201
		 * @var Freemius[]
202
		 */
203
		private static $_instances = array();
204
205
206
		/* Ctor
207
------------------------------------------------------------------------------------------------------------------*/
208
209
		private function __construct( $slug ) {
210
			$this->_slug = $slug;
211
212
			$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
213
214
			$this->_storage = FS_Key_Value_Storage::instance( 'plugin_data', $this->_slug );
215
216
			$this->_plugin_main_file_path = $this->_find_caller_plugin_file();
217
			$this->_plugin_dir_path       = plugin_dir_path( $this->_plugin_main_file_path );
218
			$this->_plugin_basename       = plugin_basename( $this->_plugin_main_file_path );
219
			$this->_free_plugin_basename  = str_replace( '-premium/', '/', $this->_plugin_basename );
220
221
			$base_name_split        = explode( '/', $this->_plugin_basename );
222
			$this->_plugin_dir_name = $base_name_split[0];
223
224
			if ( $this->_logger->is_on() ) {
225
				$this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path );
226
				$this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path );
227
				$this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename );
228
				$this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename );
229
				$this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name );
230
			}
231
232
			// Remember link between file to slug.
233
			$this->store_file_slug_map();
234
235
			// Store plugin's initial install timestamp.
236
			if ( ! isset( $this->_storage->install_timestamp ) ) {
237
				$this->_storage->install_timestamp = WP_FS__SCRIPT_START_TIME;
0 ignored issues
show
Documentation introduced by
The property install_timestamp does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
238
			}
239
240
			$this->_plugin = FS_Plugin_Manager::instance( $this->_slug )->get();
241
242
			$this->_admin_notices = FS_Admin_Notice_Manager::instance(
243
				$slug,
244
				is_object( $this->_plugin ) ? $this->_plugin->title : ''
245
			);
246
247
			if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) ) {
248
				FS_Api::clear_cache();
249
			}
250
251
			$this->_register_hooks();
252
253
			$this->_load_account();
254
255
			$this->_version_updates_handler();
256
		}
257
258
		/**
259
		 * @author Vova Feldman (@svovaf)
260
		 * @since  1.0.9
261
		 */
262
		private function _version_updates_handler() {
263
			if ( ! isset( $this->_storage->sdk_version ) || $this->_storage->sdk_version != $this->version ) {
0 ignored issues
show
Documentation introduced by
The property sdk_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
264
				// Freemius version upgrade mode.
265
				$this->_storage->sdk_last_version = $this->_storage->sdk_version;
0 ignored issues
show
Documentation introduced by
The property sdk_last_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property sdk_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
266
				$this->_storage->sdk_version      = $this->version;
0 ignored issues
show
Documentation introduced by
The property sdk_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
267
268
				if ( empty( $this->_storage->sdk_last_version ) ||
0 ignored issues
show
Documentation introduced by
The property sdk_last_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
269
				     version_compare( $this->_storage->sdk_last_version, $this->version, '<' )
0 ignored issues
show
Documentation introduced by
The property sdk_last_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
270
				) {
271
					$this->_storage->sdk_upgrade_mode   = true;
0 ignored issues
show
Documentation introduced by
The property sdk_upgrade_mode does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
272
					$this->_storage->sdk_downgrade_mode = false;
0 ignored issues
show
Documentation introduced by
The property sdk_downgrade_mode does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
273
				} else {
274
					$this->_storage->sdk_downgrade_mode = true;
0 ignored issues
show
Documentation introduced by
The property sdk_downgrade_mode does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
275
					$this->_storage->sdk_upgrade_mode   = false;
0 ignored issues
show
Documentation introduced by
The property sdk_upgrade_mode does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
276
277
				}
278
279
				$this->do_action( 'sdk_version_update' );
280
			}
281
282
			$plugin_version = $this->get_plugin_version();
283
			if ( ! isset( $this->_storage->plugin_version ) || $this->_storage->plugin_version != $plugin_version ) {
0 ignored issues
show
Documentation introduced by
The property plugin_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
284
				// Plugin version upgrade mode.
285
				$this->_storage->plugin_last_version = $this->_storage->plugin_version;
0 ignored issues
show
Documentation introduced by
The property plugin_last_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property plugin_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
286
				$this->_storage->plugin_version      = $plugin_version;
0 ignored issues
show
Documentation introduced by
The property plugin_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
287
288
				if ( empty( $this->_storage->plugin_last_version ) ||
0 ignored issues
show
Documentation introduced by
The property plugin_last_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
289
				     version_compare( $this->_storage->plugin_last_version, $plugin_version, '<' )
0 ignored issues
show
Documentation introduced by
The property plugin_last_version does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
290
				) {
291
					$this->_storage->plugin_upgrade_mode   = true;
0 ignored issues
show
Documentation introduced by
The property plugin_upgrade_mode does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
292
					$this->_storage->plugin_downgrade_mode = false;
0 ignored issues
show
Documentation introduced by
The property plugin_downgrade_mode does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
293
				} else {
294
					$this->_storage->plugin_downgrade_mode = true;
0 ignored issues
show
Documentation introduced by
The property plugin_downgrade_mode does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
295
					$this->_storage->plugin_upgrade_mode   = false;
0 ignored issues
show
Documentation introduced by
The property plugin_upgrade_mode does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
296
				}
297
298
				$this->do_action( 'plugin_version_update' );
299
			}
300
		}
301
302
		/**
303
		 * @author Vova Feldman (@svovaf)
304
		 * @since  1.0.9
305
		 */
306
		private function _register_hooks() {
307
			if ( is_admin() ) {
308
				// Hook to plugin activation
309
				register_activation_hook( $this->_plugin_main_file_path, array(
310
					&$this,
311
					'_activate_plugin_event_hook'
312
				) );
313
314
				// Hook to plugin uninstall.
315
				register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) );
316
317
				if ( ! $this->is_ajax() ) {
318
					if ( ! $this->is_addon() ) {
319
						add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY );
320
						add_action( 'admin_menu', array( &$this, '_prepare_admin_menu' ), WP_FS__LOWEST_PRIORITY );
321
					}
322
				}
323
			}
324
325
			register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) );
326
327
			add_action( 'init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY );
328
329
			$this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) );
330
		}
331
332
		/**
333
		 * @author Vova Feldman (@svovaf)
334
		 * @since  1.0.9
335
		 */
336
		private function _register_account_hooks() {
337
			if ( is_admin() ) {
338
				if ( ! $this->is_ajax() ) {
339
					if ( $this->has_trial_plan() ) {
340
						$last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false );
341
						if ( ! $this->_site->is_trial_utilized() &&
342
						     (
343
							     // Show promotion if never shown it yet and 24 hours after initial activation.
344
							     ( false === $last_time_trial_promotion_shown && $this->_storage->activation_timestamp < ( time() - WP_FS__TIME_24_HOURS_IN_SEC ) ) ||
0 ignored issues
show
Documentation introduced by
The property activation_timestamp does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
345
							     // Show promotion in every 30 days.
346
							     ( is_numeric( $last_time_trial_promotion_shown ) && 30 * WP_FS__TIME_24_HOURS_IN_SEC < time() - $last_time_trial_promotion_shown ) )
347
						) {
348
							$this->add_action( 'after_init_plugin_registered', array( &$this, '_add_trial_notice' ) );
349
						}
350
					}
351
				}
352
353
				// If user is paying or in trial and have the free version installed,
354
				// assume that the deactivation is for the upgrade process.
355
				if ( ! $this->is_paying_or_trial() || $this->is_premium() ) {
356
					add_action( 'wp_ajax_submit-uninstall-reason', array( &$this, '_submit_uninstall_reason_action' ) );
357
358
					global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
359
					if ( 'plugins.php' === $pagenow ) {
360
						add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) );
361
					}
362
				}
363
			}
364
		}
365
366
		/**
367
		 * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins
368
		 * page.
369
		 *
370
		 * @author Vova Feldman (@svovaf)
371
		 * @author Leo Fajardo (@leorw)
372
		 * @since  1.1.2
373
		 */
374
		function _add_deactivation_feedback_dialog_box() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
375
			fs_enqueue_local_style( 'fs_deactivation_feedback', '/admin/deactivation-feedback.css' );
376
377
			/* Check the type of user:
378
			 * 1. Long-term (long-term)
379
			 * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term).
380
			 * 3. Short-term (short-term)
381
			 */
382
			$is_long_term_user = true;
383
384
			// Check if the site is at least 2 days old.
385
			$time_installed = $this->_storage->install_timestamp;
0 ignored issues
show
Documentation introduced by
The property install_timestamp does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
386
387
			// Difference in seconds.
388
			$date_diff = time() - $time_installed;
389
390
			// Convert seconds to days.
391
			$date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) );
392
393
			if ( $date_diff_days < 2 ) {
394
				$is_long_term_user = false;
395
			}
396
397
			$is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user );
398
399
			if ( $is_long_term_user ) {
400
				$user_type = 'long-term';
401
			} else {
402
				if ( ! $this->is_registered() && ! $this->is_anonymous() ) {
403
					$user_type = 'non-registered-and-non-anonymous-short-term';
404
				} else {
405
					$user_type = 'short-term';
406
				}
407
			}
408
409
			$uninstall_reasons = $this->_get_uninstall_reasons( $user_type );
410
411
			// Load the HTML template for the deactivation feedback dialog box.
412
			$vars = array(
413
				'reasons' => $uninstall_reasons,
414
				'slug'    => $this->_slug
415
			);
416
417
			fs_require_once_template( 'deactivation-feedback-modal.php', $vars );
418
		}
419
420
		/**
421
		 * @author Leo Fajardo (leorw)
422
		 * @since  1.1.2
423
		 *
424
		 * @param string $user_type
425
		 *
426
		 * @return array The uninstall reasons for the specified user type.
427
		 */
428
		function _get_uninstall_reasons( $user_type = 'long-term' ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
429
			$reason_found_better_plugin = array(
430
				'id'                => 2,
431
				'text'              => __fs( 'reason-found-a-better-plugin' ),
432
				'input_type'        => 'textfield',
433
				'input_placeholder' => __fs( 'placeholder-plugin-name' )
434
			);
435
436
			$reason_other = array(
437
				'id'                => 7,
438
				'text'              => __fs( 'reason-other' ),
439
				'input_type'        => 'textfield',
440
				'input_placeholder' => ''
441
			);
442
443
			$long_term_user_reasons = array(
444
				array(
445
					'id'                => 1,
446
					'text'              => __fs( 'reason-no-longer-needed' ),
447
					'input_type'        => '',
448
					'input_placeholder' => ''
449
				),
450
				$reason_found_better_plugin,
451
				array(
452
					'id'                => 3,
453
					'text'              => __fs( 'reason-needed-for-a-short-period' ),
454
					'input_type'        => '',
455
					'input_placeholder' => ''
456
				),
457
				array(
458
					'id'                => 4,
459
					'text'              => __fs( 'reason-broke-my-site' ),
460
					'input_type'        => '',
461
					'input_placeholder' => ''
462
				),
463
				array(
464
					'id'                => 5,
465
					'text'              => __fs( 'reason-suddenly-stopped-working' ),
466
					'input_type'        => '',
467
					'input_placeholder' => ''
468
				)
469
			);
470
471
			if ( $this->is_paying() ) {
472
				$long_term_user_reasons[] = array(
473
					'id'                => 6,
474
					'text'              => __fs( 'reason-cant-pay-anymore' ),
475
					'input_type'        => 'textfield',
476
					'input_placeholder' => __fs( 'placeholder-comfortable-price' )
477
				);
478
			}
479
480
			$long_term_user_reasons[] = $reason_other;
481
482
			$uninstall_reasons = array(
483
				'long-term'                                   => $long_term_user_reasons,
484
				'non-registered-and-non-anonymous-short-term' => array(
485
					array(
486
						'id'                => 8,
487
						'text'              => __fs( 'reason-didnt-work' ),
488
						'input_type'        => '',
489
						'input_placeholder' => ''
490
					),
491
					array(
492
						'id'                => 9,
493
						'text'              => __fs( 'reason-dont-like-to-share-my-information' ),
494
						'input_type'        => '',
495
						'input_placeholder' => ''
496
					),
497
					$reason_found_better_plugin,
498
					$reason_other
499
				),
500
				'short-term'                                  => array(
501
					array(
502
						'id'                => 10,
503
						'text'              => __fs( 'reason-couldnt-make-it-work' ),
504
						'input_type'        => '',
505
						'input_placeholder' => ''
506
					),
507
					$reason_found_better_plugin,
508
					array(
509
						'id'                => 11,
510
						'text'              => __fs( 'reason-great-but-need-specific-feature' ),
511
						'input_type'        => 'textarea',
512
						'input_placeholder' => __fs( 'placeholder-feature' )
513
					),
514
					array(
515
						'id'                => 12,
516
						'text'              => __fs( 'reason-not-working' ),
517
						'input_type'        => 'textarea',
518
						'input_placeholder' => __fs( 'placeholder-share-what-didnt-work' )
519
					),
520
					array(
521
						'id'                => 13,
522
						'text'              => __fs( 'reason-not-what-i-was-looking-for' ),
523
						'input_type'        => 'textarea',
524
						'input_placeholder' => __fs( 'placeholder-what-youve-been-looking-for' )
525
					),
526
					array(
527
						'id'                => 14,
528
						'text'              => __fs( 'reason-didnt-work-as-expected' ),
529
						'input_type'        => 'textarea',
530
						'input_placeholder' => __fs( 'placeholder-what-did-you-expect' )
531
					),
532
					$reason_other
533
				)
534
			);
535
536
			$uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons );
537
538
			return $uninstall_reasons[ $user_type ];
539
		}
540
541
		/**
542
		 * Called after the user has submitted his reason for deactivating the plugin.
543
		 *
544
		 * @author Leo Fajardo (@leorw)
545
		 * @since  1.1.2
546
		 */
547
		function _submit_uninstall_reason_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
548
			if ( ! isset( $_POST['reason_id'] ) ) {
549
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method _submit_uninstall_reason_action() contains an exit expression.

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

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

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

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

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

Loading history...
564
		}
565
566
		/**
567
		 * Leverage backtrace to find caller plugin file path.
568
		 *
569
		 * @author Vova Feldman (@svovaf)
570
		 * @since  1.0.6
571
		 *
572
		 * @return string
573
		 */
574
		private function _find_caller_plugin_file() {
575
			$bt              = debug_backtrace();
576
			$abs_path_lenght = strlen( ABSPATH );
577
			$i               = 1;
578
			while (
579
				$i < count( $bt ) - 1 &&
580
				// substr is used to prevent cases where a freemius folder appears
581
				// in the path. For example, if WordPress is installed on:
582
				//  /var/www/html/some/path/freemius/path/wordpress/wp-content/...
583
				( false !== strpos( substr( fs_normalize_path( $bt[ $i ]['file'] ), $abs_path_lenght ), '/freemius/' ) ||
584
				  fs_normalize_path( dirname( dirname( $bt[ $i ]['file'] ) ) ) !== fs_normalize_path( WP_PLUGIN_DIR ) )
585
			) {
586
				$i ++;
587
			}
588
589
			return $bt[ $i ]['file'];
590
		}
591
592
		#region Instance ------------------------------------------------------------------
593
594
		/**
595
		 * Main singleton instance.
596
		 *
597
		 * @author Vova Feldman (@svovaf)
598
		 * @since  1.0.0
599
		 *
600
		 * @param $slug
601
		 *
602
		 * @return Freemius
603
		 */
604
		static function instance( $slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
605
			$slug = strtolower( $slug );
606
607
			if ( ! isset( self::$_instances[ $slug ] ) ) {
608
				if ( 0 === count( self::$_instances ) ) {
609
					self::_load_required_static();
610
				}
611
612
				self::$_instances[ $slug ] = new Freemius( $slug );
613
			}
614
615
			return self::$_instances[ $slug ];
616
		}
617
618
		/**
619
		 * @author Vova Feldman (@svovaf)
620
		 * @since  1.0.6
621
		 *
622
		 * @param string|number $slug_or_id
623
		 *
624
		 * @return bool
625
		 */
626
		private static function has_instance( $slug_or_id ) {
627
			return ! is_numeric( $slug_or_id ) ?
628
				isset( self::$_instances[ strtolower( $slug_or_id ) ] ) :
629
				( false !== self::get_instance_by_id( $slug_or_id ) );
630
		}
631
632
		/**
633
		 * @author Vova Feldman (@svovaf)
634
		 * @since  1.0.6
635
		 *
636
		 * @param $id
637
		 *
638
		 * @return false|Freemius
639
		 */
640
		static function get_instance_by_id( $id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
641
			foreach ( self::$_instances as $slug => $instance ) {
642
				if ( $id == $instance->get_id() ) {
643
					return $instance;
644
				}
645
			}
646
647
			return false;
648
		}
649
650
		/**
651
		 *
652
		 * @author Vova Feldman (@svovaf)
653
		 * @since  1.0.1
654
		 *
655
		 * @param $plugin_file
656
		 *
657
		 * @return false|Freemius
658
		 */
659
		static function get_instance_by_file( $plugin_file ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
660
			$slug = self::find_slug_by_basename( $plugin_file );
661
662
			return ( false !== $slug ) ?
663
				self::instance( $slug ) :
664
				false;
665
		}
666
667
		/**
668
		 * @author Vova Feldman (@svovaf)
669
		 * @since  1.0.6
670
		 *
671
		 * @return false|Freemius
672
		 */
673
		function get_parent_instance() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
674
			return self::get_instance_by_id( $this->_plugin->parent_plugin_id );
675
		}
676
677
		/**
678
		 * @author Vova Feldman (@svovaf)
679
		 * @since  1.0.6
680
		 *
681
		 * @param $slug_or_id
682
		 *
683
		 * @return bool|Freemius
684
		 */
685
		function get_addon_instance( $slug_or_id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
686
			return ! is_numeric( $slug_or_id ) ?
687
				self::instance( strtolower( $slug_or_id ) ) :
688
				self::get_instance_by_id( $slug_or_id );
689
		}
690
691
		#endregion ------------------------------------------------------------------
692
693
		/**
694
		 * @author Vova Feldman (@svovaf)
695
		 * @since  1.0.6
696
		 *
697
		 * @return bool
698
		 */
699
		function is_parent_plugin_installed() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
700
			return self::has_instance( $this->_plugin->parent_plugin_id );
701
		}
702
703
		/**
704
		 * Check if add-on parent plugin in activation mode.
705
		 *
706
		 * @author Vova Feldman (@svovaf)
707
		 * @since  1.0.7
708
		 *
709
		 * @return bool
710
		 */
711
		function is_parent_in_activation() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
712
			$parent_fs = $this->get_parent_instance();
713
			if ( ! is_object( $parent_fs ) ) {
714
				return false;
715
			}
716
717
			return ( $parent_fs->is_activation_mode() );
718
		}
719
720
		/**
721
		 * Is plugin in activation mode.
722
		 *
723
		 * @author Vova Feldman (@svovaf)
724
		 * @since  1.0.7
725
		 *
726
		 * @return bool
727
		 */
728
		function is_activation_mode() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
729
			return (
730
				! $this->is_registered() &&
731
				( ! $this->enable_anonymous() ||
732
				  ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) )
733
			);
734
		}
735
736
		private static $_statics_loaded = false;
737
738
		/**
739
		 * Load static resources.
740
		 *
741
		 * @author Vova Feldman (@svovaf)
742
		 * @since  1.0.1
743
		 */
744
		private static function _load_required_static() {
745
			if ( self::$_statics_loaded ) {
746
				return;
747
			}
748
749
			self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
750
751
			self::$_static_logger->entrance();
752
753
			self::$_accounts = FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true );
754
755
			// Configure which Freemius powered plugins should be auto updated.
756
//			add_filter( 'auto_update_plugin', '_include_plugins_in_auto_update', 10, 2 );
757
758
			if ( WP_FS__DEV_MODE ) {
759
				add_action( 'admin_menu', array( 'Freemius', 'add_debug_page' ) );
760
			}
761
762
			self::$_statics_loaded = true;
763
		}
764
765
		#region Debugging ------------------------------------------------------------------
766
767
		/**
768
		 * @author Vova Feldman (@svovaf)
769
		 * @since  1.0.8
770
		 */
771
		static function add_debug_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
772
			self::$_static_logger->entrance();
773
774
			$hook = add_object_page(
775
				__fs( 'freemius-debug' ),
776
				__fs( 'freemius-debug' ),
777
				'manage_options',
778
				'freemius',
779
				array( 'Freemius', '_debug_page_render' )
780
			);
781
782
			add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) );
783
		}
784
785
		/**
786
		 * @author Vova Feldman (@svovaf)
787
		 * @since  1.0.8
788
		 */
789
		static function _debug_page_actions() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
790
			if ( fs_request_is_action( 'delete_all_accounts' ) ) {
791
				check_admin_referer( 'delete_all_accounts' );
792
793
				self::$_accounts->clear( true );
794
795
				return;
796
			}
797
		}
798
799
		/**
800
		 * @author Vova Feldman (@svovaf)
801
		 * @since  1.0.8
802
		 */
803
		static function _debug_page_render() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
804
			self::$_static_logger->entrance();
805
806
			$sites          = self::get_all_sites();
807
			$users          = self::get_all_users();
808
			$addons         = self::get_all_addons();
809
			$account_addons = self::get_all_account_addons();
810
811
//			$plans    = self::get_all_plans();
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
812
//			$licenses = self::get_all_licenses();
813
814
			$vars = array(
815
				'sites'          => $sites,
816
				'users'          => $users,
817
				'addons'         => $addons,
818
				'account_addons' => $account_addons,
819
			);
820
			fs_require_once_template( 'debug.php', $vars );
821
		}
822
823
		#endregion ------------------------------------------------------------------
824
825
		#region Connectivity Issues ------------------------------------------------------------------
826
827
		/**
828
		 * Check if Freemius should be turned on for the current plugin install + version combination. The API query
829
		 * will be only invoked once per plugin version (cached locally).
830
		 *
831
		 * @author Vova Feldman (@svovaf)
832
		 * @since  1.0.9
833
		 *
834
		 * @return bool
835
		 */
836
		private function is_on() {
837
			self::$_static_logger->entrance();
838
839
			if ( isset( $this->_is_on ) ) {
840
				return $this->_is_on;
841
			}
842
843
			// If already installed then sure it's on :)
844
			if ( $this->is_registered() ) {
845
				$this->_is_on = true;
846
847
				return $this->_is_on;
848
			}
849
850
			$version = $this->get_plugin_version();
851
852
			if ( isset( $this->_storage->is_on ) ) {
853
				if ( $version == $this->_storage->is_on['version'] ) {
0 ignored issues
show
Documentation introduced by
The property is_on does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
854
					$this->_is_on = $this->_storage->is_on['is_active'];
0 ignored issues
show
Documentation introduced by
The property is_on does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
855
856
					return $this->_is_on;
857
				}
858
			}
859
860
			// Defaults to new install.
861
			$is_update = false;
862
			$is_update = $this->apply_filters( 'is_plugin_update', $is_update );
863
864
			/**
865
			 * Check anonymously if the SDK should be currently activated.
866
			 * The logic is based on whether the developer turned Freemius off,
867
			 * or set a limit to the number of activations. It's not related to
868
			 * any private information of the current WordPress instance.
869
			 *
870
			 * Note:
871
			 * Only the plugin's public key is being shared with the endpoint.
872
			 * NO private nor sensitive information is being shared.
873
			 */
874
			$result = $this->get_api_plugin_scope()->get(
875
				'is_active.json?is_update=' . json_encode( $is_update )
876
			);
877
878
			$is_active = ! isset( $result->error ) &&
879
			             isset( $result->is_active ) &&
880
			             is_bool( $result->is_active ) ?
881
				$result->is_active :
882
				false;
883
884
			$this->_storage->is_on = array(
0 ignored issues
show
Documentation introduced by
The property is_on does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
885
				'is_active' => $is_active,
886
				'timestamp' => WP_FS__SCRIPT_START_TIME,
887
				'version'   => $version,
888
			);
889
890
			$this->_is_on = $is_active;
891
892
			return $this->_is_on;
893
		}
894
895
		/**
896
		 * Check if there's any connectivity issue to Freemius API.
897
		 *
898
		 * @author Vova Feldman (@svovaf)
899
		 * @since  1.0.9
900
		 *
901
		 * @return bool
902
		 */
903
		private function has_api_connectivity() {
904
			if ( isset( $this->_has_api_connection ) ) {
905
				return $this->_has_api_connection;
906
			}
907
908
			$version = $this->get_plugin_version();
909
910
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY &&
911
			     isset( $this->_storage->connectivity_test ) &&
912
			     true === $this->_storage->connectivity_test['is_connected']
0 ignored issues
show
Documentation introduced by
The property connectivity_test does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
913
			) {
914
				unset( $this->_storage->connectivity_test );
915
			}
916
917
			if ( isset( $this->_storage->connectivity_test ) ) {
918
				if ( $version == $this->_storage->connectivity_test['version'] &&
0 ignored issues
show
Documentation introduced by
The property connectivity_test does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
919
				     $_SERVER['HTTP_HOST'] == $this->_storage->connectivity_test['host'] &&
0 ignored issues
show
Documentation introduced by
The property connectivity_test does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
920
				     $_SERVER['SERVER_ADDR'] == $this->_storage->connectivity_test['server_ip']
0 ignored issues
show
Documentation introduced by
The property connectivity_test does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
921
				) {
922
					$this->_has_api_connection = $this->_storage->connectivity_test['is_connected'];
0 ignored issues
show
Documentation introduced by
The property connectivity_test does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
923
924
					return $this->_has_api_connection;
925
				}
926
			}
927
928
			$is_connected = WP_FS__SIMULATE_NO_API_CONNECTIVITY ?
929
				false :
930
				$this->get_api_plugin_scope()->test( $this->get_anonymous_id() );
931
932
			if ( ! $is_connected ) {
933
				// 2nd try of connectivity.
934
				$pong = $this->get_api_plugin_scope()->ping( $this->get_anonymous_id() );
935
936
				if ( $this->get_api_plugin_scope()->is_valid_ping( $pong ) ) {
937
					$is_connected = true;
938
				} else {
939
					// Another API failure.
940
					$this->_add_connectivity_issue_message( $pong );
941
				}
942
			}
943
944
			$this->_storage->connectivity_test = array(
0 ignored issues
show
Documentation introduced by
The property connectivity_test does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
945
				'is_connected' => $is_connected,
946
				'host'         => $_SERVER['HTTP_HOST'],
947
				'server_ip'    => $_SERVER['SERVER_ADDR'],
948
				'version'      => $version,
949
			);
950
951
			$this->_has_api_connection = $is_connected;
952
953
			return $this->_has_api_connection;
954
		}
955
956
		/**
957
		 * Anonymous and unique site identifier (Hash).
958
		 *
959
		 * @author Vova Feldman (@svovaf)
960
		 * @since  1.1.0
961
		 *
962
		 * @return string
963
		 */
964
		function get_anonymous_id() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
965
			if ( ! self::$_accounts->has_option( 'unique_id' ) ) {
966
				$key = get_site_url();
967
968
				// If localhost, assign microtime instead of domain.
969
				if ( WP_FS__IS_LOCALHOST || false !== strpos( $key, 'localhost' ) ) {
970
					$key = microtime();
971
				}
972
973
				self::$_accounts->set_option( 'unique_id', md5( $key ), true );
974
			}
975
976
			return self::$_accounts->get_option( 'unique_id' );
977
		}
978
979
		/**
980
		 * Generate API connectivity issue message.
981
		 *
982
		 * @author Vova Feldman (@svovaf)
983
		 * @since  1.0.9
984
		 *
985
		 * @param mixed $api_result
986
		 */
987
		function _add_connectivity_issue_message( $api_result ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
988
			if ( $this->_enable_anonymous ) {
989
				// Don't add message if can run anonymously.
990
				return;
991
			}
992
993
			if ( ! function_exists( 'wp_nonce_url' ) ) {
994
				require_once( ABSPATH . 'wp-includes/functions.php' );
995
			}
996
997
			self::require_pluggable_essentials();
998
999
			$current_user = wp_get_current_user();
1000
//			$admin_email = get_option( 'admin_email' );
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1001
			$admin_email = $current_user->user_email;
1002
1003
			$message = false;
1004
			if ( is_object( $api_result ) &&
1005
			     isset( $api_result->error )
1006
			) {
1007
				switch ( $api_result->error->code ) {
1008
					case 'curl_missing':
1009
						$message = sprintf(
1010
							__fs( 'x-requires-access-to-api', 'freemius' ) . ' ' .
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
1137
						)
1138
					)
1139
				);
1140
			}
1141
1142
			$this->_admin_notices->add_sticky(
1143
				$message,
1144
				'failed_connect_api',
1145
				__fs( 'oops' ) . '...',
1146
				'error'
1147
			);
1148
		}
1149
1150
		/**
1151
		 * Get collection of all active plugins.
1152
		 *
1153
		 * @author Vova Feldman (@svovaf)
1154
		 * @since  1.0.9
1155
		 *
1156
		 * @return array[string]array
0 ignored issues
show
Documentation introduced by
The doc-type array[string]array could not be parsed: Expected "]" at position 2, but found "string". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1157
		 */
1158
		private function get_active_plugins() {
1159
			self::require_plugin_essentials();
1160
1161
			$active_plugin            = array();
1162
			$all_plugins              = get_plugins();
1163
			$active_plugins_basenames = get_option( 'active_plugins' );
1164
1165
			foreach ( $active_plugins_basenames as $plugin_basename ) {
1166
				$active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ];
1167
			}
1168
1169
			return $active_plugin;
1170
		}
1171
1172
		/**
1173
		 * Handle user request to resolve connectivity issue.
1174
		 * This method will send an email to Freemius API technical staff for resolution.
1175
		 * The email will contain server's info and installed plugins (might be caching issue).
1176
		 *
1177
		 * @author Vova Feldman (@svovaf)
1178
		 * @since  1.0.9
1179
		 */
1180
		function _email_about_firewall_issue() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1181
			$this->_admin_notices->remove_sticky( 'failed_connect_api' );
1182
1183
			self::require_pluggable_essentials();
1184
1185
			$current_user = wp_get_current_user();
1186
			$admin_email  = $current_user->user_email;
1187
1188
			$ping = $this->get_api_plugin_scope()->ping();
1189
1190
			$error_type = fs_request_get( 'error_type', 'general' );
1191
1192
			switch ( $error_type ) {
1193
				case 'squid':
1194
					$title = 'Squid ACL Blocking Issue';
1195
					break;
1196
				case 'cloudflare':
1197
					$title = 'CloudFlare Blocking Issue';
1198
					break;
1199
				default:
1200
					$title = 'API Connectivity Issue';
1201
					break;
1202
			}
1203
1204
			$custom_email_sections = array();
1205
1206
			if ( 'squid' === $error_type ) {
1207
				// Override the 'Site' email section.
1208
				$custom_email_sections['site'] = array(
1209
					'rows' => array(
1210
						'hosting_company' => array( 'Hosting Company', fs_request_get( 'hosting_company' ) )
1211
					)
1212
				);
1213
			}
1214
1215
			// Add 'API Error' custom email section.
1216
			$custom_email_sections['api_error'] = array(
1217
				'title' => 'API Error',
1218
				'rows'  => array(
1219
					'ping' => array( is_string( $ping ) ? htmlentities( $ping ) : json_encode( $ping ) )
1220
				)
1221
			);
1222
1223
			// Send email with technical details to resolve CloudFlare's firewall unnecessary protection.
1224
			$this->send_email(
1225
				'[email protected]',                              // recipient
1226
				$title . ' [' . $this->get_plugin_name() . ']',  // subject
1227
				$custom_email_sections,
1228
				array( "Reply-To: $admin_email <$admin_email>" ) // headers
1229
			);
1230
1231
			$this->_admin_notices->add_sticky(
1232
				sprintf(
1233
					__fs( 'fix-request-sent-message' ),
1234
					'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1235
				),
1236
				'server_details_sent'
1237
			);
1238
1239
			// Action was taken, tell that API connectivity troubleshooting should be off now.
1240
1241
			echo "1";
1242
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method _email_about_firewall_issue() contains an exit expression.

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

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

Loading history...
1243
		}
1244
1245
		static function _add_firewall_issues_javascript() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1246
			$params = array();
1247
			fs_require_once_template( 'firewall-issues-js.php', $params );
1248
		}
1249
1250
		#endregion Connectivity Issues ------------------------------------------------------------------
1251
1252
		#region Email ------------------------------------------------------------------
1253
1254
		/**
1255
		 * Generates and sends an HTML email with customizable sections.
1256
		 *
1257
		 * @author Leo Fajardo (@leorw)
1258
		 * @since  1.1.2
1259
		 *
1260
		 * @param string $to_address
1261
		 * @param string $subject
1262
		 * @param array  $sections
1263
		 * @param array  $headers
1264
		 *
1265
		 * @return bool Whether the email contents were sent successfully.
1266
		 */
1267
		private function send_email(
1268
			$to_address,
1269
			$subject,
1270
			$sections = array(),
1271
			$headers = array()
1272
		) {
1273
			$default_sections = $this->get_email_sections();
1274
1275
			// Insert new sections or replace the default email sections.
1276
			if ( is_array( $sections ) && ! empty( $sections ) ) {
1277
				foreach ( $sections as $section_id => $custom_section ) {
1278
					if ( ! isset( $default_sections[ $section_id ] ) ) {
1279
						// If the section does not exist, add it.
1280
						$default_sections[ $section_id ] = $custom_section;
1281
					} else {
1282
						// If the section already exists, override it.
1283
						$current_section = $default_sections[ $section_id ];
1284
1285
						// Replace the current section's title if a custom section title exists.
1286
						if ( isset( $custom_section['title'] ) ) {
1287
							$current_section['title'] = $custom_section['title'];
1288
						}
1289
1290
						// Insert new rows under the current section or replace the default rows.
1291
						if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) {
1292
							foreach ( $custom_section['rows'] as $row_id => $row ) {
1293
								$current_section['rows'][ $row_id ] = $row;
1294
							}
1295
						}
1296
1297
						$default_sections[ $section_id ] = $current_section;
1298
					}
1299
				}
1300
			}
1301
1302
			$vars    = array( 'sections' => $default_sections );
1303
			$message = fs_get_template( 'email.php', $vars );
1304
1305
			// Set the type of email to HTML.
1306
			$headers[] = 'Content-type: text/html';
1307
1308
			$header_string = implode( "\r\n", $headers );
1309
1310
			return wp_mail(
1311
				$to_address,
1312
				$subject,
1313
				$message,
1314
				$header_string
1315
			);
1316
		}
1317
1318
		/**
1319
		 * Generates the data for the sections of the email content.
1320
		 *
1321
		 * @author Leo Fajardo (@leorw)
1322
		 * @since  1.1.2
1323
		 *
1324
		 * @return array
1325
		 */
1326
		private function get_email_sections() {
1327
			self::require_pluggable_essentials();
1328
1329
			// Retrieve the current user's information so that we can get the user's email, first name, and last name below.
1330
			$current_user = wp_get_current_user();
1331
1332
			// Retrieve the cURL version information so that we can get the version number below.
1333
			$curl_version_information = curl_version();
1334
1335
			$active_plugin = $this->get_active_plugins();
1336
1337
			// Generate the list of active plugins separated by new line.
1338
			$active_plugin_string = '';
1339
			foreach ( $active_plugin as $plugin ) {
1340
				$active_plugin_string .= sprintf(
1341
					'<a href="%s">%s</a> [v%s]<br>',
1342
					$plugin['PluginURI'],
1343
					$plugin['Name'],
1344
					$plugin['Version']
1345
				);
1346
			}
1347
1348
			// Generate the default email sections.
1349
			$sections = array(
1350
				'sdk'     => array(
1351
					'title' => 'SDK',
1352
					'rows'  => array(
1353
						'fs_version'   => array( 'FS Version', $this->version ),
1354
						'curl_version' => array( 'cURL Version', $curl_version_information['version'] )
1355
					)
1356
				),
1357
				'plugin'  => array(
1358
					'title' => 'Plugin',
1359
					'rows'  => array(
1360
						'name'    => array( 'Name', $this->get_plugin_name() ),
1361
						'version' => array( 'Version', $this->get_plugin_version() )
1362
					)
1363
				),
1364
				'site'    => array(
1365
					'title' => 'Site',
1366
					'rows'  => array(
1367
						'address'     => array( 'Address', site_url() ),
1368
						'host'        => array(
1369
							'HTTP_HOST',
1370
							( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' )
1371
						),
1372
						'server_addr' => array(
1373
							'SERVER_ADDR',
1374
							( ! empty( $_SERVER['SERVER_ADDR'] ) ? '<a href="http://www.projecthoneypot.org/ip_' . $_SERVER['SERVER_ADDR'] . '">' . $_SERVER['SERVER_ADDR'] . '</a>' : '' )
1375
						)
1376
					)
1377
				),
1378
				'user'    => array(
1379
					'title' => 'User',
1380
					'rows'  => array(
1381
						'email' => array( 'Email', $current_user->user_email ),
1382
						'first' => array( 'First', $current_user->user_firstname ),
1383
						'last'  => array( 'Last', $current_user->user_lastname )
1384
					)
1385
				),
1386
				'plugins' => array(
1387
					'title' => 'Plugins',
1388
					'rows'  => array(
1389
						'active_plugins' => array( 'Active Plugins', $active_plugin_string )
1390
					)
1391
				),
1392
			);
1393
1394
			// Allow the sections to be modified by other code.
1395
			$sections = $this->apply_filters( 'email_template_sections', $sections );
1396
1397
			return $sections;
1398
		}
1399
1400
		#endregion Email ------------------------------------------------------------------
1401
1402
		#region Initialization ------------------------------------------------------------------
1403
1404
		/**
1405
		 * Init plugin's Freemius instance.
1406
		 *
1407
		 * @author Vova Feldman (@svovaf)
1408
		 * @since  1.0.1
1409
		 *
1410
		 * @param number $id
1411
		 * @param string $public_key
1412
		 * @param bool   $is_live
1413
		 * @param bool   $is_premium
1414
		 */
1415
		function init( $id, $public_key, $is_live = true, $is_premium = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1416
			$this->_logger->entrance();
1417
1418
			$this->dynamic_init( array(
1419
				'id'         => $id,
1420
				'public_key' => $public_key,
1421
				'is_live'    => $is_live,
1422
				'is_premium' => $is_premium,
1423
			) );
1424
		}
1425
1426
		private function _get_option( &$options, $key, $default = false ) {
1427
			return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
1428
		}
1429
1430
		private function _get_bool_option( &$options, $key, $default = false ) {
1431
			return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
1432
		}
1433
1434
		private function _get_numeric_option( &$options, $key, $default = false ) {
1435
			return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default;
1436
		}
1437
1438
		/**
1439
		 * Dynamic initiator, originally created to support initiation
1440
		 * with parent_id for add-ons.
1441
		 *
1442
		 * @author Vova Feldman (@svovaf)
1443
		 * @since  1.0.6
1444
		 *
1445
		 * @param array $plugin_info
1446
		 *
1447
		 * @throws Freemius_Exception
1448
		 */
1449
		function dynamic_init( array $plugin_info ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1450
			$this->_logger->entrance();
1451
1452
			$id          = $this->_get_numeric_option( $plugin_info, 'id', false );
1453
			$public_key  = $this->_get_option( $plugin_info, 'public_key', false );
1454
			$secret_key  = $this->_get_option( $plugin_info, 'secret_key', null );
1455
			$parent_id   = $this->_get_numeric_option( $plugin_info, 'parent_id', null );
1456
			$parent_name = $this->_get_option( $plugin_info, 'parent_name', null );
1457
1458
			if ( isset( $plugin_info['parent'] ) ) {
1459
				$parent_id = $this->_get_numeric_option( $plugin_info['parent'], 'id', null );
1460
//				$parent_slug       = $this->get_option( $plugin_info['parent'], 'slug', null );
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1461
//				$parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null );
1462
				$parent_name = $this->_get_option( $plugin_info['parent'], 'name', null );
1463
			}
1464
1465
			if ( false === $id ) {
1466
				throw new Freemius_Exception( 'Plugin id parameter is not set.' );
1467
			}
1468
			if ( false === $public_key ) {
1469
				throw new Freemius_Exception( 'Plugin public_key parameter is not set.' );
1470
			}
1471
1472
			$plugin = ( $this->_plugin instanceof FS_Plugin ) ?
1473
				$this->_plugin :
1474
				new FS_Plugin();
1475
1476
			$plugin->update( array(
1477
				'id'               => $id,
1478
				'public_key'       => $public_key,
1479
				'slug'             => $this->_slug,
1480
				'parent_plugin_id' => $parent_id,
1481
				'version'          => $this->get_plugin_version(),
1482
				'title'            => $this->get_plugin_name(),
1483
				'file'             => $this->_free_plugin_basename,
1484
				'is_premium'       => $this->_get_bool_option( $plugin_info, 'is_premium', true ),
1485
				'is_live'          => $this->_get_bool_option( $plugin_info, 'is_live', true ),
1486
//				'secret_key' => $secret_key,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1487
			) );
1488
1489
			if ( $plugin->is_updated() ) {
1490
				// Update plugin details.
1491
				$this->_plugin = FS_Plugin_Manager::instance( $this->_slug )->store( $plugin );
1492
			}
1493
			$this->_plugin->secret_key = $secret_key;
1494
1495
			if ( ! isset( $plugin_info['menu'] ) ) {
1496
				// Back compatibility to 1.1.2
1497
				$plugin_info['menu'] = array(
1498
					'slug' => isset( $plugin_info['menu_slug'] ) ?
1499
						$plugin_info['menu_slug'] :
1500
						$this->_slug
1501
				);
1502
			}
1503
1504
			$this->_menu = FS_Admin_Menu_Manager::instance( $this->_slug );
1505
			$this->_menu->init( $plugin_info['menu'], $this->is_addon() );
1506
1507
			$this->_has_addons       = $this->_get_bool_option( $plugin_info, 'has_addons', false );
1508
			$this->_has_paid_plans   = $this->_get_bool_option( $plugin_info, 'has_paid_plans', true );
1509
			$this->_is_org_compliant = $this->_get_bool_option( $plugin_info, 'is_org_compliant', true );
1510
			$this->_enable_anonymous = $this->_get_bool_option( $plugin_info, 'enable_anonymous', true );
1511
1512
			if ( ! $this->is_registered() ) {
1513
				if ( ! $this->has_api_connectivity() ) {
1514
					if ( is_admin() && $this->_admin_notices->has_sticky( 'failed_connect_api' ) ) {
1515
						if ( ! $this->_enable_anonymous ) {
1516
							// If anonymous mode is disabled, add firewall admin-notice message.
1517
							add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) );
1518
1519
							add_action( "wp_ajax_{$this->_slug}_resolve_firewall_issues", array(
1520
								&$this,
1521
								'_email_about_firewall_issue'
1522
							) );
1523
						}
1524
					}
1525
1526
					// Turn Freemius off.
1527
					$this->_is_on = false;
1528
1529
					return;
1530
				}
1531
1532
				// Check if Freemius is on for the current plugin.
1533
				// This MUST be executed after all the plugin variables has been loaded.
1534
				if ( ! $this->is_on() ) {
1535
					return;
1536
				}
1537
			}
1538
1539
			if ( false === $this->_background_sync() ) {
1540
				// If background sync wasn't executed,
1541
				// and if the plugin declared it has add-ons but
1542
				// no add-ons found in the local data, then try to sync add-ons.
1543
				if ( $this->_has_addons &&
1544
				     ! $this->is_addon() &&
1545
				     ( false === $this->get_addons() )
1546
				) {
1547
					$this->_sync_addons();
1548
				}
1549
			}
1550
1551
			if ( $this->is_addon() ) {
1552
				if ( $this->is_parent_plugin_installed() ) {
1553
					// Link to parent FS.
1554
					$this->_parent = self::get_instance_by_id( $parent_id );
1555
1556
					// Get parent plugin reference.
1557
					$this->_parent_plugin = $this->_parent->get_plugin();
1558
				}
1559
			}
1560
1561
			if ( is_admin() ) {
1562
				global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
1563
				if ( 'plugins.php' === $pagenow ) {
1564
					$this->hook_plugin_action_links();
1565
				}
1566
1567
				if ( $this->is_addon() ) {
1568
					if ( ! $this->is_parent_plugin_installed() ) {
1569
						$this->_admin_notices->add(
1570
							( is_string( $parent_name ) ?
1571
								sprintf( __fs( 'addon-cannot-run-without-x' ), $this->get_plugin_name(), $parent_name ) :
1572
								sprintf( __fs( 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() )
1573
							),
1574
							__fs( 'oops' ) . '...',
1575
							'error'
1576
						);
1577
1578
						return;
1579
					} else {
1580
						if ( $this->_parent->is_registered() && ! $this->is_registered() ) {
1581
							// If parent plugin activated, automatically install add-on for the user.
1582
							$this->_activate_addon_account( $this->_parent );
1583
						}
1584
1585
						// @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic.
1586
						if ( $this->is_premium() ) {
1587
							// Remove add-on download admin-notice.
1588
							$this->_parent->_admin_notices->remove_sticky( 'addon_plan_upgraded_' . $this->_slug );
1589
						}
1590
					}
1591
				} else {
1592
					add_action( 'admin_init', array( &$this, '_admin_init_action' ) );
1593
1594
					if ( $this->_has_addons() &&
1595
					     'plugin-information' === fs_request_get( 'tab', false ) &&
1596
					     $this->get_id() == fs_request_get( 'parent_plugin_id', false )
1597
					) {
1598
						// Remove default plugin information action.
1599
						remove_all_actions( 'install_plugins_pre_plugin-information' );
1600
1601
						require_once WP_FS__DIR_INCLUDES . '/fs-plugin-functions.php';
1602
1603
						// Override action with custom plugins function for add-ons.
1604
						add_action( 'install_plugins_pre_plugin-information', 'fs_install_plugin_information' );
1605
1606
						// Override request for plugin information for Add-ons.
1607
						add_filter( 'fs_plugins_api', array( &$this, '_get_addon_info_filter' ), 10, 3 );
1608
					} else {
1609
						if ( $this->is_paying() || $this->_has_addons() ) {
1610
							new FS_Plugin_Updater( $this );
1611
						}
1612
					}
1613
				}
1614
1615
//				if ( $this->is_registered() ||
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1616
//				     $this->is_anonymous() ||
1617
//				     $this->is_pending_activation()
1618
//				) {
1619
//					$this->_init_admin();
1620
//				}
1621
			}
1622
1623
			$this->do_action( 'initiated' );
1624
1625
			if ( ! $this->is_addon() ) {
1626
				if ( $this->is_registered() ) {
1627
					// Fix for upgrade from versions < 1.0.9.
1628
					if ( ! isset( $this->_storage->activation_timestamp ) ) {
1629
						$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
0 ignored issues
show
Documentation introduced by
The property activation_timestamp does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
1630
					}
1631
					if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) {
0 ignored issues
show
Documentation introduced by
The property prev_is_premium does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
1632
						if ( isset( $this->_storage->prev_is_premium ) ) {
1633
							add_action( is_admin() ? 'admin_init' : 'init', array(
1634
								&$this,
1635
								'_plugin_code_type_changed'
1636
							) );
1637
						} else {
1638
							// Set for code type for the first time.
1639
							$this->_storage->prev_is_premium = $this->_plugin->is_premium;
0 ignored issues
show
Documentation introduced by
The property prev_is_premium does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
1640
						}
1641
					}
1642
1643
					$this->do_action( 'after_init_plugin_registered' );
1644
				} else if ( $this->is_anonymous() ) {
1645
					$this->do_action( 'after_init_plugin_anonymous' );
1646
				} else if ( $this->is_pending_activation() ) {
1647
					$this->do_action( 'after_init_plugin_pending_activations' );
1648
				}
1649
			} else {
1650
				if ( $this->is_registered() ) {
1651
					$this->do_action( 'after_init_addon_registered' );
1652
				} else if ( $this->is_anonymous() ) {
1653
					$this->do_action( 'after_init_addon_anonymous' );
1654
				} else if ( $this->is_pending_activation() ) {
1655
					$this->do_action( 'after_init_addon_pending_activations' );
1656
				}
1657
			}
1658
		}
1659
1660
		/**
1661
		 * Handles plugin's code type change (free <--> premium).
1662
		 *
1663
		 * @author Vova Feldman (@svovaf)
1664
		 * @since  1.0.9
1665
		 */
1666
		function _plugin_code_type_changed() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1667
			// Send code type changes event.
1668
			$this->sync_install();
1669
1670
			if ( $this->is_premium() ) {
1671
				// Activated premium code.
1672
				$this->do_action( 'after_premium_version_activation' );
1673
1674
				// Remove all sticky messages related to download of the premium version.
1675
				$this->_admin_notices->remove_sticky( array(
1676
					'trial_started',
1677
					'plan_upgraded',
1678
					'plan_changed',
1679
				) );
1680
1681
				$this->_admin_notices->add_sticky(
1682
					__fs( 'premium-activated-message' ),
1683
					'premium_activated',
1684
					__fs( 'woot' ) . '!'
1685
				);
1686
			} else {
1687
				// Activated free code (after had the premium before).
1688
				$this->do_action( 'after_free_version_reactivation' );
1689
1690
				if ( $this->is_paying() && ! $this->is_premium() ) {
1691
					$this->_admin_notices->add_sticky(
1692
						sprintf(
1693
							__fs( 'you-have-x-license' ),
1694
							$this->_site->plan->title
1695
						) . ' ' . $this->_get_latest_download_link( sprintf(
1696
							__fs( 'download-x-version-now' ),
1697
							$this->_site->plan->title
1698
						) ),
1699
						'plan_upgraded',
1700
						__fs( 'yee-haw' ) . '!'
1701
					);
1702
				}
1703
			}
1704
1705
			// Update is_premium of latest version.
1706
			$this->_storage->prev_is_premium = $this->_plugin->is_premium;
0 ignored issues
show
Documentation introduced by
The property prev_is_premium does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
1707
		}
1708
1709
		#endregion Initialization ------------------------------------------------------------------
1710
1711
		#region Add-ons -------------------------------------------------------------------------
1712
1713
		/**
1714
		 * Generate add-on plugin information.
1715
		 *
1716
		 * @author Vova Feldman (@svovaf)
1717
		 * @since  1.0.6
1718
		 *
1719
		 * @param array       $data
1720
		 * @param string      $action
1721
		 * @param object|null $args
1722
		 *
1723
		 * @return array|null
1724
		 */
1725
		function _get_addon_info_filter( $data, $action = '', $args = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1726
			$this->_logger->entrance();
1727
1728
			$parent_plugin_id = fs_request_get( 'parent_plugin_id', false );
1729
1730
			if ( $this->get_id() != $parent_plugin_id ||
1731
			     ( 'plugin_information' !== $action ) ||
1732
			     ! isset( $args->slug )
1733
			) {
1734
				return $data;
1735
			}
1736
1737
			// Find add-on by slug.
1738
			$addons         = $this->get_addons();
1739
			$selected_addon = false;
1740
			foreach ( $addons as $addon ) {
0 ignored issues
show
Bug introduced by
The expression $addons of type array<integer,object<FS_Plugin>>|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
1741
				if ( $addon->slug == $args->slug ) {
1742
					$selected_addon = $addon;
1743
					break;
1744
				}
1745
			}
1746
1747
			if ( false === $selected_addon ) {
1748
				return $data;
1749
			}
1750
1751
			if ( ! isset( $selected_addon->info ) ) {
1752
				// Setup some default info.
1753
				$selected_addon->info                  = new stdClass();
1754
				$selected_addon->info->selling_point_0 = 'Selling Point 1';
1755
				$selected_addon->info->selling_point_1 = 'Selling Point 2';
1756
				$selected_addon->info->selling_point_2 = 'Selling Point 3';
1757
				$selected_addon->info->description     = '<p>Tell your users all about your add-on</p>';
1758
			}
1759
1760
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
1761
1762
			$data = $args;
1763
1764
			// Fetch as much as possible info from local files.
1765
			$plugin_local_data = $this->get_plugin_data();
1766
			$data->name        = $selected_addon->title;
1767
			$data->author      = $plugin_local_data['Author'];
1768
			$view_vars         = array( 'plugin' => $selected_addon );
1769
			$data->sections    = array(
1770
				'description' => fs_get_template( '/plugin-info/description.php', $view_vars ),
1771
			);
1772
1773
			if ( ! empty( $selected_addon->info->banner_url ) ) {
1774
				$data->banners = array(
1775
					'low' => $selected_addon->info->banner_url,
1776
				);
1777
			}
1778
1779
			if ( ! empty( $selected_addon->info->screenshots ) ) {
1780
				$view_vars                     = array( 'screenshots' => $selected_addon->info->screenshots );
1781
				$data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars );
1782
			}
1783
1784
			// Load add-on pricing.
1785
			$has_pricing  = false;
1786
			$has_features = false;
1787
			$plans        = false;
1788
			$plans_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$selected_addon->id}/plans.json" );
1789
			if ( ! isset( $plans_result->error ) ) {
1790
				$plans = $plans_result->plans;
1791
				if ( is_array( $plans ) ) {
1792
					foreach ( $plans as &$plan ) {
1793
						$pricing_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$selected_addon->id}/plans/{$plan->id}/pricing.json" );
1794
						if ( ! isset( $pricing_result->error ) ) {
1795
							// Update plan's pricing.
1796
							$plan->pricing = $pricing_result->pricing;
1797
1798
							$has_pricing = true;
1799
						}
1800
1801
						$features_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$selected_addon->id}/plans/{$plan->id}/features.json" );
1802
						if ( ! isset( $features_result->error ) &&
1803
						     is_array( $features_result->features ) &&
1804
						     0 < count( $features_result->features )
1805
						) {
1806
							// Update plan's pricing.
1807
							$plan->features = $features_result->features;
1808
1809
							$has_features = true;
1810
						}
1811
					}
1812
				}
1813
			}
1814
1815
			// Get latest add-on version.
1816
			$latest = $this->_fetch_latest_version( $selected_addon->id );
1817
1818
			if ( is_object( $latest ) ) {
1819
				$data->version      = $latest->version;
1820
				$data->last_updated = ! is_null( $latest->updated ) ? $latest->updated : $latest->created;
1821
				$data->requires     = $latest->requires_platform_version;
1822
				$data->tested       = $latest->tested_up_to_version;
1823
			} else {
1824
				// Add dummy version.
1825
				$data->version = '1.0.0';
1826
1827
				// Add message to developer to deploy the plugin through Freemius.
1828
			}
1829
1830
			$data->checkout_link = $this->checkout_url();
1831
			$data->download_link = 'https://dummy.com';
1832
1833
			if ( $has_pricing ) {
1834
				// Add plans to data.
1835
				$data->plans = $plans;
1836
1837
				if ( $has_features ) {
1838
					$view_vars                  = array( 'plans' => $plans );
1839
					$data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars );
1840
				}
1841
			}
1842
1843
			return $data;
1844
		}
1845
1846
		/**
1847
		 * Check if add-on installed and activated on site.
1848
		 *
1849
		 * @author Vova Feldman (@svovaf)
1850
		 * @since  1.0.6
1851
		 *
1852
		 * @param string|number $slug_or_id
1853
		 *
1854
		 * @return bool
1855
		 */
1856
		function is_addon_activated( $slug_or_id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

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

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

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

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

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

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

Let’s take a look at an example:

class User
{
    private $email;

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

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

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

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

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

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
1889
			}
1890
1891
			return $slug . '/' . $slug . '.php';
1892
		}
1893
1894
		/**
1895
		 * Get installed add-ons instances.
1896
		 *
1897
		 * @author Vova Feldman (@svovaf)
1898
		 * @since  1.0.6
1899
		 *
1900
		 * @return Freemius[]
1901
		 */
1902
		function get_installed_addons() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1903
			$installed_addons = array();
1904
			foreach ( self::$_instances as $slug => $instance ) {
1905
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
1906
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
1907
						$installed_addons[] = $instance;
1908
					}
1909
				}
1910
			}
1911
1912
			return $installed_addons;
1913
		}
1914
1915
		/**
1916
		 * Check if any add-ons of the plugin are installed.
1917
		 *
1918
		 * @author Leo Fajardo (@leorw)
1919
		 * @since  1.1.1
1920
		 *
1921
		 * @return bool
1922
		 */
1923
		function has_installed_addons() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1924
			if ( ! $this->_has_addons() ) {
1925
				return false;
1926
			}
1927
1928
			foreach ( self::$_instances as $slug => $instance ) {
1929
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
1930
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
1931
						return true;
1932
					}
1933
				}
1934
			}
1935
1936
			return false;
1937
		}
1938
1939
		/**
1940
		 * Tell Freemius that the current plugin is an add-on.
1941
		 *
1942
		 * @author Vova Feldman (@svovaf)
1943
		 * @since  1.0.6
1944
		 *
1945
		 * @param number $parent_plugin_id The parent plugin ID
1946
		 */
1947
		function init_addon( $parent_plugin_id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1948
			$this->_plugin->parent_plugin_id = $parent_plugin_id;
1949
		}
1950
1951
		/**
1952
		 * @author Vova Feldman (@svovaf)
1953
		 * @since  1.0.6
1954
		 *
1955
		 * @return bool
1956
		 */
1957
		function is_addon() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1958
			return isset( $this->_plugin->parent_plugin_id ) && is_numeric( $this->_plugin->parent_plugin_id );
1959
		}
1960
1961
		#endregion ------------------------------------------------------------------
1962
1963
		#region Sandbox ------------------------------------------------------------------
1964
1965
		/**
1966
		 * Set Freemius into sandbox mode for debugging.
1967
		 *
1968
		 * @author Vova Feldman (@svovaf)
1969
		 * @since  1.0.4
1970
		 *
1971
		 * @param string $secret_key
1972
		 */
1973
		function init_sandbox( $secret_key ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1974
			$this->_plugin->secret_key = $secret_key;
1975
1976
			// Update plugin details.
1977
			FS_Plugin_Manager::instance( $this->_slug )->update( $this->_plugin, true );
1978
		}
1979
1980
		/**
1981
		 * Check if running payments in sandbox mode.
1982
		 *
1983
		 * @author Vova Feldman (@svovaf)
1984
		 * @since  1.0.4
1985
		 *
1986
		 * @return bool
1987
		 */
1988
		function is_payments_sandbox() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1989
			return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key );
1990
		}
1991
1992
		#endregion Sandbox ------------------------------------------------------------------
1993
1994
		/**
1995
		 * Check if running test vs. live plugin.
1996
		 *
1997
		 * @author Vova Feldman (@svovaf)
1998
		 * @since  1.0.5
1999
		 *
2000
		 * @return bool
2001
		 */
2002
		function is_live() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2003
			return $this->_plugin->is_live;
2004
		}
2005
2006
		/**
2007
		 * Check if the user skipped connecting the account with Freemius.
2008
		 *
2009
		 * @author Vova Feldman (@svovaf)
2010
		 * @since  1.0.7
2011
		 *
2012
		 * @return bool
2013
		 */
2014
		function is_anonymous() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2015
			if ( ! isset( $this->_is_anonymous ) ) {
2016
				if ( ! isset( $this->_storage->is_anonymous ) ) {
2017
					// Not skipped.
2018
					$this->_is_anonymous = false;
2019
				} else if ( is_bool( $this->_storage->is_anonymous ) ) {
0 ignored issues
show
Documentation introduced by
The property is_anonymous does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
2020
					// For back compatibility, since the variable was boolean before.
2021
					$this->_is_anonymous = $this->_storage->is_anonymous;
0 ignored issues
show
Documentation introduced by
The property is_anonymous does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
2022
2023
					// Upgrade stored data format to 1.1.3 format.
2024
					$this->set_anonymous_mode( $this->_storage->is_anonymous );
0 ignored issues
show
Documentation introduced by
The property is_anonymous does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
2025
				} else {
2026
					// Version 1.1.3 and higher.
2027
					$this->_is_anonymous = $this->_storage->is_anonymous['is'];
0 ignored issues
show
Documentation introduced by
The property is_anonymous does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
2028
				}
2029
			}
2030
2031
			return $this->_is_anonymous;
2032
		}
2033
2034
		/**
2035
		 * Check if user connected his account and install pending email activation.
2036
		 *
2037
		 * @author Vova Feldman (@svovaf)
2038
		 * @since  1.0.7
2039
		 *
2040
		 * @return bool
2041
		 */
2042
		function is_pending_activation() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2043
			return $this->_storage->get( 'is_pending_activation', false );
2044
		}
2045
2046
		/**
2047
		 * Check if plugin must be WordPress.org compliant.
2048
		 *
2049
		 * @since 1.0.7
2050
		 *
2051
		 * @return bool
2052
		 */
2053
		function is_org_repo_compliant() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2054
			return $this->_is_org_compliant;
2055
		}
2056
2057
		/**
2058
		 * Background sync every 24 hours.
2059
		 *
2060
		 * @author Vova Feldman (@svovaf)
2061
		 * @since  1.0.4
2062
		 *
2063
		 * @return bool If function actually executed the sync in this iteration.
2064
		 */
2065
		private function _background_sync() {
2066
			$this->_logger->entrance();
2067
2068
			// Don't sync license on AJAX calls.
2069
			if ( $this->is_ajax() ) {
2070
				return false;
2071
			}
2072
2073
			// Asked to sync explicitly, no need for background sync.
2074
			if ( fs_request_is_action( $this->_slug . '_sync_license' ) ) {
2075
				return false;
2076
			}
2077
2078
			$sync_timestamp = $this->_storage->get( 'sync_timestamp' );
2079
2080
			if ( ! is_numeric( $sync_timestamp ) || $sync_timestamp >= time() ) {
2081
				// If updated not set or happens to be in the future, set as if was 24 hours earlier.
2082
				$sync_timestamp                 = time() - WP_FS__TIME_24_HOURS_IN_SEC;
2083
				$this->_storage->sync_timestamp = $sync_timestamp;
0 ignored issues
show
Documentation introduced by
The property sync_timestamp does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
2084
			}
2085
2086
			if ( ( defined( 'WP_FS__DEV_MODE' ) && WP_FS__DEV_MODE && fs_request_has( 'background_sync' ) ) ||
2087
			     ( $sync_timestamp <= time() - WP_FS__TIME_24_HOURS_IN_SEC )
2088
			) {
2089
2090
				if ( $this->is_registered() ) {
2091
					// Initiate background plan sync.
2092
					$this->_sync_license( true );
2093
2094
					// Check for plugin updates.
2095
					$this->_check_updates( true );
2096
				}
2097
2098
				if ( ! $this->is_addon() ) {
2099
					if ( $this->is_registered() || $this->_has_addons ) {
2100
						// Try to fetch add-ons if registered or if plugin
2101
						// declared that it has add-ons.
2102
						$this->_sync_addons();
2103
					}
2104
				}
2105
2106
				// Update last sync timestamp.
2107
				$this->_storage->sync_timestamp = time();
0 ignored issues
show
Documentation introduced by
The property sync_timestamp does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
2108
2109
				return true;
2110
			}
2111
2112
			return false;
2113
		}
2114
2115
		/**
2116
		 * Show a notice that activation is currently pending.
2117
		 *
2118
		 * @author Vova Feldman (@svovaf)
2119
		 * @since  1.0.7
2120
		 *
2121
		 * @param bool|string $email
2122
		 */
2123
		function _add_pending_activation_notice( $email = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2124
			if ( ! is_string( $email ) ) {
2125
				$current_user = wp_get_current_user();
2126
				$email        = $current_user->user_email;
2127
			}
2128
2129
			$this->_admin_notices->add_sticky(
2130
				sprintf(
2131
					__fs( 'pending-activation-message' ),
2132
					'<b>' . $this->get_plugin_name() . '</b>',
2133
					'<b>' . $email . '</b>'
2134
				),
2135
				'activation_pending',
2136
				'Thanks!'
2137
			);
2138
		}
2139
2140
		/**
2141
		 *
2142
		 * NOTE: admin_menu action executed before admin_init.
2143
		 *
2144
		 * @author Vova Feldman (@svovaf)
2145
		 * @since  1.0.7
2146
		 */
2147
		function _admin_init_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2148
			// Automatically redirect to connect/activation page after plugin activation.
2149
			if ( get_option( "fs_{$this->_slug}_activated", false ) ) {
2150
				delete_option( "fs_{$this->_slug}_activated" );
2151
				$this->_redirect_on_activation_hook();
2152
2153
				return;
2154
			}
2155
2156
			if ( fs_request_is_action( $this->_slug . '_skip_activation' ) ) {
2157
				check_admin_referer( $this->_slug . '_skip_activation' );
2158
2159
				$this->skip_connection();
2160
2161
				if ( fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) ) ) {
2162
					exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _admin_init_action() contains an exit expression.

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

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

Loading history...
2163
				}
2164
			}
2165
2166
			if ( ! $this->is_addon() && ! $this->is_registered() && ! $this->is_anonymous() ) {
2167
				if ( ! $this->is_pending_activation() ) {
2168
					if ( ! $this->_menu->is_activation_page() ) {
2169
						$this->_admin_notices->add(
2170
							sprintf(
2171
								__fs( 'you-are-step-away' ),
2172
								sprintf( '<b><a href="%s">%s</a></b>',
2173
									$this->get_activation_url(),
2174
									sprintf( __fs( 'activate-x-now' ), $this->get_plugin_name() )
2175
								)
2176
							),
2177
							'',
2178
							'update-nag'
2179
						);
2180
					}
2181
				}
2182
			}
2183
2184
			$this->_add_upgrade_action_link();
2185
		}
2186
2187
		/**
2188
		 * Return current page's URL.
2189
		 *
2190
		 * @author Vova Feldman (@svovaf)
2191
		 * @since  1.0.7
2192
		 *
2193
		 * @return string
2194
		 */
2195
		function current_page_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2196
			$url = 'http';
2197
2198
			if ( isset( $_SERVER["HTTPS"] ) ) {
2199
				if ( $_SERVER["HTTPS"] == "on" ) {
2200
					$url .= "s";
2201
				}
2202
			}
2203
			$url .= "://";
2204
			if ( $_SERVER["SERVER_PORT"] != "80" ) {
2205
				$url .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
2206
			} else {
2207
				$url .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
2208
			}
2209
2210
			return esc_url( $url );
2211
		}
2212
2213
		/**
2214
		 * Check if the current page is the plugin's main admin settings page.
2215
		 *
2216
		 * @author Vova Feldman (@svovaf)
2217
		 * @since  1.0.7
2218
		 *
2219
		 * @return bool
2220
		 */
2221
		function _is_plugin_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2222
			return fs_is_plugin_page( $this->_menu->get_raw_slug() ) ||
2223
			       fs_is_plugin_page( $this->_slug );
2224
		}
2225
2226
		/* Events
2227
		------------------------------------------------------------------------------------------------------------------*/
2228
		/**
2229
		 * Delete site install from Database.
2230
		 *
2231
		 * @author Vova Feldman (@svovaf)
2232
		 * @since  1.0.1
2233
		 *
2234
		 * @param bool $store
2235
		 */
2236
		function _delete_site( $store = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2237
			$sites = self::get_all_sites();
2238
2239
			if ( isset( $sites[ $this->_slug ] ) ) {
2240
				unset( $sites[ $this->_slug ] );
2241
			}
2242
2243
			self::$_accounts->set_option( 'sites', $sites, $store );
2244
		}
2245
2246
		/**
2247
		 * Delete plugin's plans information.
2248
		 *
2249
		 * @param bool $store Flush to Database if true.
2250
		 *
2251
		 * @author Vova Feldman (@svovaf)
2252
		 * @since  1.0.9
2253
		 */
2254
		private function _delete_plans( $store = true ) {
2255
			$this->_logger->entrance();
2256
2257
			$plans = self::get_all_plans();
2258
2259
			unset( $plans[ $this->_slug ] );
2260
2261
			self::$_accounts->set_option( 'plans', $plans, $store );
2262
		}
2263
2264
		/**
2265
		 * Delete all plugin licenses.
2266
		 *
2267
		 * @author Vova Feldman (@svovaf)
2268
		 * @since  1.0.9
2269
		 *
2270
		 * @param bool        $store
2271
		 * @param string|bool $plugin_slug
2272
		 */
2273
		private function _delete_licenses( $store = true, $plugin_slug = false ) {
2274
			$this->_logger->entrance();
2275
2276
			$all_licenses = self::get_all_licenses();
2277
2278
			if ( ! is_string( $plugin_slug ) ) {
2279
				$plugin_slug = $this->_slug;
2280
			}
2281
2282
			unset( $all_licenses[ $plugin_slug ] );
2283
2284
			self::$_accounts->set_option( 'licenses', $all_licenses, $store );
2285
		}
2286
2287
		/**
2288
		 * Plugin activated hook.
2289
		 *
2290
		 * @author Vova Feldman (@svovaf)
2291
		 * @since  1.0.1
2292
		 * @uses   FS_Api
2293
		 */
2294
		function _activate_plugin_event_hook() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2295
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2296
2297
			if ( ! current_user_can( 'activate_plugins' ) ) {
2298
				return;
2299
			}
2300
2301
			// Clear API cache on activation.
2302
			FS_Api::clear_cache();
2303
2304
			if ( $this->is_registered() ) {
2305
				// Send re-activation event and sync.
2306
				$this->sync_install( array(), true );
2307
2308
				/**
2309
				 * @todo Work on automatic deactivation of the Free plugin version. It doesn't work since the slug of the free & premium versions is identical. Therefore, only one instance of Freemius is created and the activation hook of the premium version is not being added.
2310
				 */
2311
				if ( $this->_plugin_basename !== $this->_free_plugin_basename ) {
2312
					// Deactivate Free plugin version on premium plugin activation.
2313
					deactivate_plugins( $this->_free_plugin_basename );
2314
2315
					$this->_admin_notices->add(
2316
						sprintf( __fs( 'successful-version-upgrade-message' ), sprintf( '<b>%s</b>', $this->_plugin->title ) ),
2317
						__fs( 'woot' ) . '!'
2318
					);
2319
				}
2320
			} else if ( $this->is_anonymous() ) {
2321
				/**
2322
				 * Reset "skipped" click cache on the following:
2323
				 *  1. Development mode.
2324
				 *  2. If the user skipped the exact same version before.
2325
				 *
2326
				 * @todo 3. If explicitly asked to retry after every activation.
2327
				 */
2328
				if ( WP_FS__DEV_MODE ||
2329
				     $this->get_plugin_version() == $this->_storage->is_anonymous['version']
0 ignored issues
show
Documentation introduced by
The property is_anonymous does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
2330
				) {
2331
					$this->reset_anonymous_mode();
2332
				}
2333
			}
2334
2335
			if ( $this->has_api_connectivity() ) {
2336
				// Store hint that the plugin was just activated to enable auto-redirection to settings.
2337
				add_option( "fs_{$this->_slug}_activated", true );
2338
			}
2339
		}
2340
2341
		/**
2342
		 * Delete account.
2343
		 *
2344
		 * @author Vova Feldman (@svovaf)
2345
		 * @since  1.0.3
2346
		 *
2347
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
2348
		 */
2349
		function delete_account_event( $check_user = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2350
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2351
2352
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
2353
				return;
2354
			}
2355
2356
			$this->do_action( 'before_account_delete' );
2357
2358
			// Clear all admin notices.
2359
			$this->_admin_notices->clear_all_sticky();
2360
2361
			$this->_delete_site( false );
2362
2363
			$this->_delete_plans( false );
2364
2365
			$this->_delete_licenses( false );
2366
2367
			// Delete add-ons related to plugin's account.
2368
			$this->_delete_account_addons( false );
2369
2370
			// @todo Delete plans and licenses of add-ons.
2371
2372
			self::$_accounts->store();
2373
2374
			// Clear all storage data.
2375
			$this->_storage->clear_all( true, array(
2376
				'connectivity_test',
2377
				'is_on',
2378
			) );
2379
2380
			// Send delete event.
2381
			$this->get_api_site_scope()->call( '/', 'delete' );
2382
2383
			$this->do_action( 'after_account_delete' );
2384
		}
2385
2386
		/**
2387
		 * Plugin deactivation hook.
2388
		 *
2389
		 * @author Vova Feldman (@svovaf)
2390
		 * @since  1.0.1
2391
		 */
2392
		function _deactivate_plugin_hook() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2393
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2394
2395
			if ( ! current_user_can( 'activate_plugins' ) ) {
2396
				return;
2397
			}
2398
2399
			$this->_admin_notices->clear_all_sticky();
2400
2401
			if ( ! $this->has_api_connectivity() ) {
2402
				// Reset connectivity test cache.
2403
				unset( $this->_storage->connectivity_test );
2404
			}
2405
2406
			if ( $this->is_registered() ) {
2407
				// Send deactivation event.
2408
				$this->sync_install( array(
2409
					'is_active' => false,
2410
				) );
2411
			}
2412
2413
			// Clear API cache on deactivation.
2414
			FS_Api::clear_cache();
2415
		}
2416
2417
		/**
2418
		 * @author Vova Feldman (@svovaf)
2419
		 * @since  1.1.3
2420
		 *
2421
		 * @param bool $is_anonymous
2422
		 */
2423
		private function set_anonymous_mode( $is_anonymous = true ) {
2424
			// Store information regarding skip to try and opt-in the user
2425
			// again in the future.
2426
			$this->_storage->is_anonymous = array(
0 ignored issues
show
Documentation introduced by
The property is_anonymous does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
2427
				'is'        => $is_anonymous,
2428
				'timestamp' => WP_FS__SCRIPT_START_TIME,
2429
				'version'   => $this->get_plugin_version(),
2430
			);
2431
		}
2432
2433
		/**
2434
		 * @author Vova Feldman (@svovaf)
2435
		 * @since  1.1.3
2436
		 */
2437
		private function reset_anonymous_mode() {
2438
			unset( $this->_storage->is_anonymous );
2439
		}
2440
2441
		/**
2442
		 * Skip account connect, and set anonymous mode.
2443
		 *
2444
		 * @author Vova Feldman (@svovaf)
2445
		 * @since  1.1.1
2446
		 */
2447
		private function skip_connection() {
2448
			$this->_logger->entrance();
2449
2450
			$this->set_anonymous_mode();
2451
2452
			// Send anonymous skip event.
2453
			// No user identified info nor any tracking will be sent after the user skips the opt-in.
2454
			$this->get_api_plugin_scope()->call( 'skip.json', 'put', array(
2455
				'uid' => $this->get_anonymous_id(),
2456
			) );
2457
		}
2458
2459
		/**
2460
		 * Plugin version update hook.
2461
		 *
2462
		 * @author Vova Feldman (@svovaf)
2463
		 * @since  1.0.4
2464
		 */
2465
		private function update_plugin_version_event() {
2466
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2467
2468
			$this->_site->version = $this->get_plugin_version();
2469
2470
			// Send update event.
2471
			$site = $this->send_install_update( array(), true );
2472
2473
			if ( false !== $site && ! $this->is_api_error( $site ) ) {
2474
				$this->_site = new FS_Site( $site );
2475
				$this->_store_site( true );
2476
			}
2477
		}
2478
2479
		/**
2480
		 * Update install details.
2481
		 *
2482
		 * @author Vova Feldman (@svovaf)
2483
		 * @since  1.1.2
2484
		 *
2485
		 * @param string[] string $override
2486
		 *
2487
		 * @return array
2488
		 */
2489
		private function get_install_data_for_api( $override = array() ) {
2490
			return array_merge( array(
2491
				'version'                      => $this->get_plugin_version(),
2492
				'is_premium'                   => $this->is_premium(),
2493
				'language'                     => get_bloginfo( 'language' ),
2494
				'charset'                      => get_bloginfo( 'charset' ),
2495
				'platform_version'             => get_bloginfo( 'version' ),
2496
				'programming_language_version' => phpversion(),
2497
				'title'                        => get_bloginfo( 'name' ),
2498
				'url'                          => get_site_url(),
2499
				// Special params.
2500
				'is_active'                    => true,
2501
				'is_uninstalled'               => false,
2502
			), $override );
2503
		}
2504
2505
		/**
2506
		 * Update install only if changed.
2507
		 *
2508
		 * @author Vova Feldman (@svovaf)
2509
		 * @since  1.0.9
2510
		 *
2511
		 * @param string[] string $override
2512
		 * @param bool     $flush
2513
		 *
2514
		 * @return false|object|string
2515
		 */
2516
		private function send_install_update( $override = array(), $flush = false ) {
2517
			$this->_logger->entrance();
2518
2519
			$check_properties = $this->get_install_data_for_api( $override );
2520
2521
			if ( $flush ) {
2522
				$params = $check_properties;
2523
			} else {
2524
				$params           = array();
2525
				$special          = array();
2526
				$special_override = false;
2527
2528
				foreach ( $check_properties as $p => $v ) {
2529
					if ( property_exists( $this->_site, $p ) ) {
2530
						if ( ! empty( $this->_site->{$p} ) &&
2531
						     $this->_site->{$p} != $v
2532
						) {
2533
							$this->_site->{$p} = $v;
2534
							$params[ $p ]      = $v;
2535
						}
2536
					} else {
2537
						$special[ $p ] = $v;
2538
2539
						if ( isset( $override[ $p ] ) ) {
2540
							$special_override = true;
2541
						}
2542
					}
2543
				}
2544
2545
				if ( $special_override || 0 < count( $params ) ) {
2546
					// Add special params only if has at least one
2547
					// standard param, or if explicitly requested to
2548
					// override a special param or a pram which is not exist
2549
					// in the install object.
2550
					$params = array_merge( $params, $special );
2551
				}
2552
			}
2553
2554
			if ( 0 < count( $params ) ) {
2555
				// Send updated values to FS.
2556
				return $this->get_api_site_scope()->call( '/', 'put', $params );
2557
			}
2558
2559
			return false;
2560
		}
2561
2562
		/**
2563
		 * Update install only if changed.
2564
		 *
2565
		 * @author Vova Feldman (@svovaf)
2566
		 * @since  1.0.9
2567
		 *
2568
		 * @param string[] string $override
2569
		 * @param bool     $flush
2570
		 *
2571
		 * @return false|object|string
2572
		 */
2573
		private function sync_install( $override = array(), $flush = false ) {
2574
			$this->_logger->entrance();
2575
2576
			$site = $this->send_install_update( $override, $flush );
2577
2578
			if ( false === $site ) {
2579
				// No sync required.
2580
				return;
2581
			}
2582
2583
			if ( $this->is_api_error( $site ) ) {
2584
				// Failed to sync, don't update locally.
2585
				return;
2586
			}
2587
2588
			$plan              = $this->get_plan();
2589
			$this->_site       = new FS_Site( $site );
2590
			$this->_site->plan = $plan;
2591
2592
			$this->_store_site( true );
2593
		}
2594
2595
		/**
2596
		 * Plugin uninstall hook.
2597
		 *
2598
		 * @author Vova Feldman (@svovaf)
2599
		 * @since  1.0.1
2600
		 *
2601
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
2602
		 */
2603
		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...
2604
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2605
2606
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
2607
				return;
2608
			}
2609
2610
			$params = array();
2611
			if ( isset( $this->_storage->uninstall_reason ) ) {
2612
				$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...
2613
				$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...
2614
			}
2615
2616
			if ( ! $this->is_registered() && isset( $this->_storage->uninstall_reason ) ) {
2617
				// Send anonymous uninstall event only if user submitted a feedback.
2618
				$params['uid'] = $this->get_anonymous_id();
2619
				$this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params );
2620
			} else {
2621
				// Send uninstall event.
2622
				$this->send_install_update( array_merge( $params, array(
2623
					'is_active'      => false,
2624
					'is_uninstalled' => true,
2625
				) ) );
2626
			}
2627
2628
			// @todo Decide if we want to delete plugin information from db.
2629
		}
2630
2631
		/**
2632
		 * @author Vova Feldman (@svovaf)
2633
		 * @since  1.1.1
2634
		 *
2635
		 * @return string
2636
		 */
2637
		private function premium_plugin_basename() {
2638
			return preg_replace( '/\//', '-premium/', $this->_free_plugin_basename, 1 );
2639
		}
2640
2641
		/**
2642
		 * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking.
2643
		 *
2644
		 * @author Vova Feldman (@svovaf)
2645
		 * @since  1.0.2
2646
		 */
2647
		public static function _uninstall_plugin_hook() {
2648
			self::_load_required_static();
2649
2650
			self::$_static_logger->entrance();
2651
2652
			if ( ! current_user_can( 'activate_plugins' ) ) {
2653
				return;
2654
			}
2655
2656
			$plugin_file = substr( current_filter(), strlen( 'uninstall_' ) );
2657
2658
			self::$_static_logger->info( 'plugin = ' . $plugin_file );
2659
2660
			define( 'WP_FS__UNINSTALL_MODE', true );
2661
2662
			$fs = self::get_instance_by_file( $plugin_file );
2663
2664
			if ( is_object( $fs ) ) {
2665
				self::require_plugin_essentials();
2666
2667
				if ( is_plugin_active( $fs->_free_plugin_basename ) ||
2668
				     is_plugin_active( $fs->premium_plugin_basename() )
2669
				) {
2670
					// Deleting Free or Premium plugin version while the other version still installed.
2671
					return;
2672
				}
2673
2674
				$fs->_uninstall_plugin_event();
2675
2676
				$fs->do_action( 'after_uninstall' );
2677
			}
2678
		}
2679
2680
		#region Plugin Information ------------------------------------------------------------------
2681
2682
		/**
2683
		 * Load WordPress core plugin.php essential module.
2684
		 *
2685
		 * @author Vova Feldman (@svovaf)
2686
		 * @since  1.1.1
2687
		 */
2688
		private static function require_plugin_essentials() {
2689
			if ( ! function_exists( 'get_plugins' ) ) {
2690
				require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
2691
			}
2692
		}
2693
2694
		/**
2695
		 * Load WordPress core pluggable.php module.
2696
		 *
2697
		 * @author Vova Feldman (@svovaf)
2698
		 * @since  1.1.2
2699
		 */
2700
		private static function require_pluggable_essentials() {
2701
			if ( ! function_exists( 'wp_get_current_user' ) ) {
2702
				require_once( ABSPATH . 'wp-includes/pluggable.php' );
2703
			}
2704
		}
2705
2706
		/**
2707
		 * Return plugin data.
2708
		 *
2709
		 * @author Vova Feldman (@svovaf)
2710
		 * @since  1.0.1
2711
		 *
2712
		 * @return array
2713
		 */
2714
		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...
2715
			if ( ! isset( $this->_plugin_data ) ) {
2716
				self::require_plugin_essentials();
2717
2718
				$this->_plugin_data = get_plugin_data( $this->_plugin_main_file_path );
2719
			}
2720
2721
			return $this->_plugin_data;
2722
		}
2723
2724
		/**
2725
		 * @author Vova Feldman (@svovaf)
2726
		 * @since  1.0.1
2727
		 *
2728
		 * @return string Plugin slug.
2729
		 */
2730
		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...
2731
			return $this->_slug;
2732
		}
2733
2734
		/**
2735
		 * @author Vova Feldman (@svovaf)
2736
		 * @since  1.0.1
2737
		 *
2738
		 * @return number Plugin ID.
2739
		 */
2740
		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...
2741
			return $this->_plugin->id;
2742
		}
2743
2744
		/**
2745
		 * @author Vova Feldman (@svovaf)
2746
		 * @since  1.0.1
2747
		 *
2748
		 * @return string Plugin public key.
2749
		 */
2750
		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...
2751
			return $this->_plugin->public_key;
2752
		}
2753
2754
		/**
2755
		 * Will be available only on sandbox mode.
2756
		 *
2757
		 * @author Vova Feldman (@svovaf)
2758
		 * @since  1.0.4
2759
		 *
2760
		 * @return mixed Plugin secret key.
2761
		 */
2762
		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...
2763
			return $this->_plugin->secret_key;
2764
		}
2765
2766
		/**
2767
		 * @author Vova Feldman (@svovaf)
2768
		 * @since  1.1.1
2769
		 *
2770
		 * @return bool
2771
		 */
2772
		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...
2773
			return ! empty( $this->_plugin->secret_key );
2774
		}
2775
2776
		/**
2777
		 * @author Vova Feldman (@svovaf)
2778
		 * @since  1.0.9
2779
		 *
2780
		 * @return string
2781
		 */
2782
		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...
2783
			$this->_logger->entrance();
2784
2785
			if ( ! isset( $this->_plugin_name ) ) {
2786
				$plugin_data = $this->get_plugin_data();
2787
2788
				// Get name.
2789
				$this->_plugin_name = $plugin_data['Name'];
2790
2791
				// Check if plugin name contains [Premium] suffix and remove it.
2792
				$suffix     = '[premium]';
2793
				$suffix_len = strlen( $suffix );
2794
2795
				if ( strlen( $plugin_data['Name'] ) > $suffix_len &&
2796
				     $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len )
2797
				) {
2798
					$this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len );
2799
				}
2800
2801
				$this->_logger->departure( 'Name = ' . $this->_plugin_name );
2802
			}
2803
2804
			return $this->_plugin_name;
2805
		}
2806
2807
		/**
2808
		 * @author Vova Feldman (@svovaf)
2809
		 * @since  1.0.0
2810
		 *
2811
		 * @return string
2812
		 */
2813
		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...
2814
			$this->_logger->entrance();
2815
2816
			$plugin_data = $this->get_plugin_data();
2817
2818
			$this->_logger->departure( 'Version = ' . $plugin_data['Version'] );
2819
2820
			return $plugin_data['Version'];
2821
		}
2822
2823
		/**
2824
		 * @author Vova Feldman (@svovaf)
2825
		 * @since  1.0.4
2826
		 *
2827
		 * @return string
2828
		 */
2829
		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...
2830
			return $this->_plugin_basename;
2831
		}
2832
2833
		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...
2834
			$this->_logger->entrance();
2835
2836
			$plugin_folder = $this->_plugin_basename;
2837
2838
			while ( '.' !== dirname( $plugin_folder ) ) {
2839
				$plugin_folder = dirname( $plugin_folder );
2840
			}
2841
2842
			$this->_logger->departure( 'Folder Name = ' . $plugin_folder );
2843
2844
			return $plugin_folder;
2845
		}
2846
2847
		#endregion ------------------------------------------------------------------
2848
2849
		/* Account
2850
		------------------------------------------------------------------------------------------------------------------*/
2851
2852
		/**
2853
		 * Find plugin's slug by plugin's basename.
2854
		 *
2855
		 * @author Vova Feldman (@svovaf)
2856
		 * @since  1.0.9
2857
		 *
2858
		 * @param string $plugin_base_name
2859
		 *
2860
		 * @return false|string
2861
		 */
2862
		private static function find_slug_by_basename( $plugin_base_name ) {
2863
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
2864
2865
			if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) {
2866
				return false;
2867
			}
2868
2869
			return $file_slug_map[ $plugin_base_name ];
2870
		}
2871
2872
		/**
2873
		 * Store the map between the plugin's basename to the slug.
2874
		 *
2875
		 * @author Vova Feldman (@svovaf)
2876
		 * @since  1.0.9
2877
		 */
2878
		private function store_file_slug_map() {
2879
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
2880
2881
			if ( ! array( $file_slug_map ) ) {
2882
				$file_slug_map = array();
2883
			}
2884
2885
			if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) ||
2886
			     $file_slug_map[ $this->_plugin_basename ] !== $this->_slug
2887
			) {
2888
				$file_slug_map[ $this->_plugin_basename ] = $this->_slug;
2889
				self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true );
2890
			}
2891
		}
2892
2893
		/**
2894
		 * @return FS_User[]
2895
		 */
2896
		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...
2897
			$users = self::$_accounts->get_option( 'users', array() );
2898
2899
			if ( ! is_array( $users ) ) {
2900
				$users = array();
2901
			}
2902
2903
			return $users;
2904
		}
2905
2906
		/**
2907
		 * @return FS_Site[]
2908
		 */
2909
		private static function get_all_sites() {
2910
			$sites = self::$_accounts->get_option( 'sites', array() );
2911
2912
			if ( ! is_array( $sites ) ) {
2913
				$sites = array();
2914
			}
2915
2916
			return $sites;
2917
		}
2918
2919
		/**
2920
		 * @author Vova Feldman (@svovaf)
2921
		 * @since  1.0.6
2922
		 *
2923
		 * @return FS_Plugin_License[]
2924
		 */
2925
		private static function get_all_licenses() {
2926
			$licenses = self::$_accounts->get_option( 'licenses', array() );
2927
2928
			if ( ! is_array( $licenses ) ) {
2929
				$licenses = array();
2930
			}
2931
2932
			return $licenses;
2933
		}
2934
2935
		/**
2936
		 * @return FS_Plugin_Plan[]
2937
		 */
2938
		private static function get_all_plans() {
2939
			$plans = self::$_accounts->get_option( 'plans', array() );
2940
2941
			if ( ! is_array( $plans ) ) {
2942
				$plans = array();
2943
			}
2944
2945
			return $plans;
2946
		}
2947
2948
		/**
2949
		 * @author Vova Feldman (@svovaf)
2950
		 * @since  1.0.4
2951
		 *
2952
		 * @return FS_Plugin_Tag[]
2953
		 */
2954
		private static function get_all_updates() {
2955
			$updates = self::$_accounts->get_option( 'updates', array() );
2956
2957
			if ( ! is_array( $updates ) ) {
2958
				$updates = array();
2959
			}
2960
2961
			return $updates;
2962
		}
2963
2964
		/**
2965
		 * @author Vova Feldman (@svovaf)
2966
		 * @since  1.0.6
2967
		 *
2968
		 * @return FS_Plugin[]|false
2969
		 */
2970
		private static function get_all_addons() {
2971
			$addons = self::$_accounts->get_option( 'addons', array() );
2972
2973
			if ( ! is_array( $addons ) ) {
2974
				$addons = array();
2975
			}
2976
2977
			return $addons;
2978
		}
2979
2980
		/**
2981
		 * @author Vova Feldman (@svovaf)
2982
		 * @since  1.0.6
2983
		 *
2984
		 * @return FS_Plugin[]|false
2985
		 */
2986
		private static function get_all_account_addons() {
2987
			$addons = self::$_accounts->get_option( 'account_addons', array() );
2988
2989
			if ( ! is_array( $addons ) ) {
2990
				$addons = array();
2991
			}
2992
2993
			return $addons;
2994
		}
2995
2996
		/**
2997
		 * Check if user is registered.
2998
		 *
2999
		 * @author Vova Feldman (@svovaf)
3000
		 * @since  1.0.1
3001
		 * @return bool
3002
		 */
3003
		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...
3004
			return is_object( $this->_user );
3005
		}
3006
3007
		/**
3008
		 * @author Vova Feldman (@svovaf)
3009
		 * @since  1.0.4
3010
		 *
3011
		 * @return FS_Plugin
3012
		 */
3013
		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...
3014
			return $this->_plugin;
3015
		}
3016
3017
		/**
3018
		 * @author Vova Feldman (@svovaf)
3019
		 * @since  1.0.3
3020
		 *
3021
		 * @return FS_User
3022
		 */
3023
		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...
3024
			return $this->_user;
3025
		}
3026
3027
		/**
3028
		 * @author Vova Feldman (@svovaf)
3029
		 * @since  1.0.3
3030
		 *
3031
		 * @return FS_Site
3032
		 */
3033
		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...
3034
			return $this->_site;
3035
		}
3036
3037
		/**
3038
		 * @author Vova Feldman (@svovaf)
3039
		 * @since  1.0.6
3040
		 *
3041
		 * @return FS_Plugin[]|false
3042
		 */
3043
		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...
3044
			$this->_logger->entrance();
3045
3046
			$addons = self::get_all_addons();
3047
3048
			if ( ! is_array( $addons ) ||
3049
			     ! isset( $addons[ $this->_plugin->id ] ) ||
3050
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
3051
			     0 === count( $addons[ $this->_plugin->id ] )
3052
			) {
3053
				return false;
3054
			}
3055
3056
			return $addons[ $this->_plugin->id ];
3057
		}
3058
3059
		/**
3060
		 * @author Vova Feldman (@svovaf)
3061
		 * @since  1.0.6
3062
		 *
3063
		 * @return FS_Plugin[]|false
3064
		 */
3065
		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...
3066
			$this->_logger->entrance();
3067
3068
			$addons = self::get_all_account_addons();
3069
3070
			if ( ! is_array( $addons ) ||
3071
			     ! isset( $addons[ $this->_plugin->id ] ) ||
3072
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
3073
			     0 === count( $addons[ $this->_plugin->id ] )
3074
			) {
3075
				return false;
3076
			}
3077
3078
			return $addons[ $this->_plugin->id ];
3079
		}
3080
3081
		/**
3082
		 * Get add-on by ID (from local data).
3083
		 *
3084
		 * @author Vova Feldman (@svovaf)
3085
		 * @since  1.0.6
3086
		 *
3087
		 * @param number $id
3088
		 *
3089
		 * @return FS_Plugin|false
3090
		 */
3091
		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...
3092
			$this->_logger->entrance();
3093
3094
			$addons = $this->get_addons();
3095
3096
			if ( is_array( $addons ) ) {
3097
				foreach ( $addons as $addon ) {
3098
					if ( $id == $addon->id ) {
3099
						return $addon;
3100
					}
3101
				}
3102
			}
3103
3104
			return false;
3105
		}
3106
3107
		/**
3108
		 * Get add-on by slug (from local data).
3109
		 *
3110
		 * @author Vova Feldman (@svovaf)
3111
		 * @since  1.0.6
3112
		 *
3113
		 * @param string $slug
3114
		 *
3115
		 * @return FS_Plugin|false
3116
		 */
3117
		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...
3118
			$this->_logger->entrance();
3119
3120
			$addons = $this->get_addons();
3121
3122
			if ( is_array( $addons ) ) {
3123
				foreach ( $addons as $addon ) {
3124
					if ( $slug == $addon->slug ) {
3125
						return $addon;
3126
					}
3127
				}
3128
			}
3129
3130
			return false;
3131
		}
3132
3133
		#region Plans & Licensing ------------------------------------------------------------------
3134
3135
		/**
3136
		 * Check if running premium plugin code.
3137
		 *
3138
		 * @author Vova Feldman (@svovaf)
3139
		 * @since  1.0.5
3140
		 *
3141
		 * @return bool
3142
		 */
3143
		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...
3144
			return $this->_plugin->is_premium;
3145
		}
3146
3147
		/**
3148
		 * Get site's plan ID.
3149
		 *
3150
		 * @author Vova Feldman (@svovaf)
3151
		 * @since  1.0.2
3152
		 *
3153
		 * @return number
3154
		 */
3155
		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...
3156
			return $this->_site->plan->id;
3157
		}
3158
3159
		/**
3160
		 * Get site's plan title.
3161
		 *
3162
		 * @author Vova Feldman (@svovaf)
3163
		 * @since  1.0.2
3164
		 *
3165
		 * @return string
3166
		 */
3167
		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...
3168
			return $this->_site->plan->title;
3169
		}
3170
3171
		/**
3172
		 * @author Vova Feldman (@svovaf)
3173
		 * @since  1.0.9
3174
		 *
3175
		 * @return FS_Plugin_Plan
3176
		 */
3177
		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...
3178
			return is_object( $this->_site->plan ) ? $this->_site->plan : false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression is_object($this->_site->...s->_site->plan : false; of type FS_Plugin_Plan|false adds false to the return on line 3178 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...
3179
		}
3180
3181
		/**
3182
		 * @author Vova Feldman (@svovaf)
3183
		 * @since  1.0.3
3184
		 *
3185
		 * @return bool
3186
		 */
3187
		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...
3188
			$this->_logger->entrance();
3189
3190
			if ( ! $this->is_registered() ) {
3191
				return false;
3192
			}
3193
3194
			// Paid plan beats trial.
3195
			return $this->is_free_plan() && $this->_site->is_trial();
3196
		}
3197
3198
		/**
3199
		 * Check if trial already utilized.
3200
		 *
3201
		 * @since 1.0.9
3202
		 *
3203
		 * @return bool
3204
		 */
3205
		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...
3206
			$this->_logger->entrance();
3207
3208
			if ( ! $this->is_registered() ) {
3209
				return false;
3210
			}
3211
3212
			return $this->_site->is_trial_utilized();
3213
		}
3214
3215
		/**
3216
		 * Get trial plan information (if in trial).
3217
		 *
3218
		 * @author Vova Feldman (@svovaf)
3219
		 * @since  1.0.9
3220
		 *
3221
		 * @return bool|FS_Plugin_Plan
3222
		 */
3223
		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...
3224
			$this->_logger->entrance();
3225
3226
			if ( ! $this->is_trial() ) {
3227
				return false;
3228
			}
3229
3230
			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...
3231
		}
3232
3233
		/**
3234
		 * Check if the user has an activated and valid paid license on current plugin's install.
3235
		 *
3236
		 * @since 1.0.9
3237
		 *
3238
		 * @return bool
3239
		 */
3240
		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...
3241
			$this->_logger->entrance();
3242
3243
			if ( ! $this->is_registered() ) {
3244
				return false;
3245
			}
3246
3247
			return (
3248
				! $this->is_trial() &&
3249
				'free' !== $this->_site->plan->name &&
3250
				$this->has_features_enabled_license()
3251
			);
3252
		}
3253
3254
		/**
3255
		 * @author Vova Feldman (@svovaf)
3256
		 * @since  1.0.4
3257
		 *
3258
		 * @return bool
3259
		 */
3260
		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...
3261
			if ( ! $this->is_registered() ) {
3262
				return true;
3263
			}
3264
3265
			return (
3266
				'free' === $this->_site->plan->name ||
3267
				! $this->has_features_enabled_license()
3268
			);
3269
		}
3270
3271
		/**
3272
		 * @author Vova Feldman (@svovaf)
3273
		 * @since  1.0.5
3274
		 *
3275
		 * @return bool
3276
		 */
3277
		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...
3278
			$this->_logger->entrance();
3279
3280
			$premium_license = $this->_get_available_premium_license();
3281
3282
			return ( false !== $premium_license );
3283
		}
3284
3285
		/**
3286
		 * @author Vova Feldman (@svovaf)
3287
		 * @since  1.0.5
3288
		 *
3289
		 * @return FS_Plugin_License
3290
		 */
3291
		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...
3292
			$this->_logger->entrance();
3293
3294
			if ( is_array( $this->_licenses ) ) {
3295
				foreach ( $this->_licenses as $license ) {
3296
					if ( ! $license->is_utilized() && $license->is_features_enabled() ) {
3297
						return $license;
3298
					}
3299
				}
3300
			}
3301
3302
			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...
3303
		}
3304
3305
		/**
3306
		 * Sync local plugin plans with remote server.
3307
		 *
3308
		 * @author Vova Feldman (@svovaf)
3309
		 * @since  1.0.5
3310
		 *
3311
		 * @return FS_Plugin_Plan[]|object
3312
		 */
3313
		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...
3314
			$plans = $this->_fetch_plugin_plans();
3315
			if ( ! $this->is_api_error( $plans ) ) {
3316
				$this->_plans = $plans;
3317
				$this->_store_plans();
3318
			}
3319
3320
			$this->do_action( 'after_plans_sync', $plans );
3321
3322
			return $this->_plans;
3323
		}
3324
3325
		/**
3326
		 * @author Vova Feldman (@svovaf)
3327
		 * @since  1.0.5
3328
		 *
3329
		 * @param number $id
3330
		 *
3331
		 * @return FS_Plugin_Plan
3332
		 */
3333
		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...
3334
			$this->_logger->entrance();
3335
3336
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
3337
				$this->_sync_plans();
3338
			}
3339
3340
			foreach ( $this->_plans as $plan ) {
3341
				if ( $id == $plan->id ) {
3342
					return $plan;
3343
				}
3344
			}
3345
3346
			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...
3347
		}
3348
3349
		/**
3350
		 * Sync local plugin plans with remote server.
3351
		 *
3352
		 * @author Vova Feldman (@svovaf)
3353
		 * @since  1.0.6
3354
		 *
3355
		 * @return FS_Plugin_License[]|object
3356
		 */
3357
		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...
3358
			$licenses = $this->_fetch_licenses();
3359
			if ( ! isset( $licenses->error ) ) {
3360
				$this->_licenses = $licenses;
3361
				$this->_store_licenses();
3362
			}
3363
3364
			// Update current license.
3365
			if ( is_object( $this->_license ) ) {
3366
				$this->_license = $this->_get_license_by_id( $this->_license->id );
3367
			}
3368
3369
			return $this->_licenses;
3370
		}
3371
3372
		/**
3373
		 * @author Vova Feldman (@svovaf)
3374
		 * @since  1.0.5
3375
		 *
3376
		 * @param number $id
3377
		 *
3378
		 * @return FS_Plugin_License
3379
		 */
3380
		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...
3381
			$this->_logger->entrance();
3382
3383
			if ( ! is_numeric( $id ) ) {
3384
				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...
3385
			}
3386
3387
			if ( ! is_array( $this->_licenses ) || 0 === count( $this->_licenses ) ) {
3388
				$this->_sync_licenses();
3389
			}
3390
3391
			foreach ( $this->_licenses as $license ) {
3392
				if ( $id == $license->id ) {
3393
					return $license;
3394
				}
3395
			}
3396
3397
			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...
3398
		}
3399
3400
		/**
3401
		 * Sync site's license with user licenses.
3402
		 *
3403
		 * @author Vova Feldman (@svovaf)
3404
		 * @since  1.0.6
3405
		 *
3406
		 * @param FS_Plugin_License|null $new_license
3407
		 */
3408
		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...
3409
			$this->_logger->entrance();
3410
3411
			$this->_license = $new_license;
3412
3413
			if ( ! is_object( $new_license ) ) {
3414
				$this->_site->license_id = null;
3415
				$this->_sync_site_subscription( null );
3416
3417
				return;
3418
			}
3419
3420
			$this->_site->license_id = $this->_license->id;
3421
3422
			if ( ! is_array( $this->_licenses ) ) {
3423
				$this->_licenses = array();
3424
			}
3425
3426
			$is_license_found = false;
3427
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
3428
				if ( $new_license->id == $this->_licenses[ $i ]->id ) {
3429
					$this->_licenses[ $i ] = $new_license;
3430
3431
					$is_license_found = true;
3432
					break;
3433
				}
3434
			}
3435
3436
			// If new license just append.
3437
			if ( ! $is_license_found ) {
3438
				$this->_licenses[] = $new_license;
3439
			}
3440
3441
			$this->_sync_site_subscription( $new_license );
3442
		}
3443
3444
		/**
3445
		 * Sync site's subscription.
3446
		 *
3447
		 * @author Vova Feldman (@svovaf)
3448
		 * @since  1.0.9
3449
		 *
3450
		 * @param FS_Plugin_License|null $license
3451
		 *
3452
		 * @return bool|\FS_Subscription
3453
		 */
3454
		private function _sync_site_subscription( $license ) {
3455
			if ( ! is_object( $license ) ) {
3456
				unset( $this->_storage->subscription );
3457
3458
				return false;
3459
			}
3460
3461
			// Load subscription details if not lifetime.
3462
			$subscription = $license->is_lifetime() ?
3463
				false :
3464
				$this->_fetch_site_license_subscription();
3465
3466
			if ( is_object( $subscription ) && ! isset( $subscription->error ) ) {
3467
				$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...
3468
			} else {
3469
				unset( $this->_storage->subscription );
3470
			}
3471
3472
			return $subscription;
3473
		}
3474
3475
		/**
3476
		 * @author Vova Feldman (@svovaf)
3477
		 * @since  1.0.6
3478
		 *
3479
		 * @return bool|\FS_Plugin_License
3480
		 */
3481
		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...
3482
			return $this->_license;
3483
		}
3484
3485
		/**
3486
		 * @return bool|\FS_Subscription
3487
		 */
3488
		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...
3489
			return isset( $this->_storage->subscription ) ?
3490
				$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...
3491
				false;
3492
		}
3493
3494
		/**
3495
		 * @author Vova Feldman (@svovaf)
3496
		 * @since  1.0.2
3497
		 *
3498
		 * @param string $plan  Plan name
3499
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
3500
		 *
3501
		 * @return bool
3502
		 */
3503
		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...
3504
			$this->_logger->entrance();
3505
3506
			if ( ! $this->is_registered() ) {
3507
				return false;
3508
			}
3509
3510
			$plan = strtolower( $plan );
3511
3512
			if ( $this->_site->plan->name === $plan ) // Exact plan.
3513
			{
3514
				return true;
3515
			} else if ( $exact ) // Required exact, but plans are different.
3516
			{
3517
				return false;
3518
			}
3519
3520
			$current_plan_order  = - 1;
3521
			$required_plan_order = - 1;
3522
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
3523
				if ( $plan === $this->_plans[ $i ]->name ) {
3524
					$required_plan_order = $i;
3525
				} else if ( $this->_site->plan->name === $this->_plans[ $i ]->name ) {
3526
					$current_plan_order = $i;
3527
				}
3528
			}
3529
3530
			return ( $current_plan_order > $required_plan_order );
3531
		}
3532
3533
		/**
3534
		 * Check if plan based on trial. If not in trial mode, should return false.
3535
		 *
3536
		 * @since  1.0.9
3537
		 *
3538
		 * @param string $plan  Plan name
3539
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
3540
		 *
3541
		 * @return bool
3542
		 */
3543
		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...
3544
			$this->_logger->entrance();
3545
3546
			if ( ! $this->is_registered() ) {
3547
				return false;
3548
			}
3549
3550
			if ( ! $this->is_trial() ) {
3551
				return false;
3552
			}
3553
3554
			if ( ! isset( $this->_storage->trial_plan ) ) {
3555
				// Store trial plan information.
3556
				$this->_enrich_site_trial_plan( true );
3557
			}
3558
3559
			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...
3560
			{
3561
				return true;
3562
			} else if ( $exact ) // Required exact, but plans are different.
3563
			{
3564
				return false;
3565
			}
3566
3567
			$current_plan_order  = - 1;
3568
			$required_plan_order = - 1;
3569
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
3570
				if ( $plan === $this->_plans[ $i ]->name ) {
3571
					$required_plan_order = $i;
3572
				} 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...
3573
					$current_plan_order = $i;
3574
				}
3575
			}
3576
3577
			return ( $current_plan_order > $required_plan_order );
3578
		}
3579
3580
		/**
3581
		 * Check if plugin has any paid plans.
3582
		 *
3583
		 * @author Vova Feldman (@svovaf)
3584
		 * @since  1.0.7
3585
		 *
3586
		 * @return bool
3587
		 */
3588
		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...
3589
			return $this->_has_paid_plans || FS_Plan_Manager::instance()->has_paid_plan( $this->_plans );
3590
		}
3591
3592
		/**
3593
		 * Check if plugin has any plan with a trail.
3594
		 *
3595
		 * @author Vova Feldman (@svovaf)
3596
		 * @since  1.0.9
3597
		 *
3598
		 * @return bool
3599
		 */
3600
		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...
3601
			if ( ! $this->is_registered() ) {
3602
				return false;
3603
			}
3604
3605
			return $this->_storage->get( 'has_trial_plan', false );
3606
		}
3607
3608
		/**
3609
		 * Check if plugin has any free plan, or is it premium only.
3610
		 *
3611
		 * Note: If no plans configured, assume plugin is free.
3612
		 *
3613
		 * @author Vova Feldman (@svovaf)
3614
		 * @since  1.0.7
3615
		 *
3616
		 * @return bool
3617
		 */
3618
		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...
3619
			return FS_Plan_Manager::instance()->has_free_plan( $this->_plans );
3620
		}
3621
3622
		#region URL Generators
3623
3624
		/**
3625
		 * Alias to pricing_url().
3626
		 *
3627
		 * @author Vova Feldman (@svovaf)
3628
		 * @since  1.0.2
3629
		 *
3630
		 * @uses   pricing_url
3631
		 *
3632
		 * @param string $period Billing cycle
3633
		 *
3634
		 * @return string
3635
		 */
3636
		function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

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

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

Loading history...
3663
			$this->_logger->entrance();
3664
3665
			return $this->_get_admin_page_url( 'pricing', array( 'billing_cycle' => $period ) );
3666
		}
3667
3668
		/**
3669
		 * Checkout page URL.
3670
		 *
3671
		 * @author Vova Feldman (@svovaf)
3672
		 * @since  1.0.6
3673
		 *
3674
		 * @param string      $period Billing cycle
3675
		 * @param bool|string $plan_name
3676
		 * @param bool|number $plan_id
3677
		 * @param bool|int    $licenses
3678
		 *
3679
		 * @return string
3680
		 */
3681
		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...
3682
			$period = WP_FS__PERIOD_ANNUALLY,
3683
			$plan_name = false,
3684
			$plan_id = false,
3685
			$licenses = false
3686
		) {
3687
			$this->_logger->entrance();
3688
3689
			$params = array(
3690
				'checkout'      => 'true',
3691
				'billing_cycle' => $period,
3692
			);
3693
3694
			if ( false !== $plan_name ) {
3695
				$params['plan_name'] = $plan_name;
3696
			}
3697
			if ( false !== $plan_id ) {
3698
				$params['plan_id'] = $plan_id;
3699
			}
3700
			if ( false !== $licenses ) {
3701
				$params['licenses'] = $licenses;
3702
			}
3703
3704
			return $this->_get_admin_page_url( 'pricing', $params );
3705
		}
3706
3707
		#endregion
3708
3709
		#endregion ------------------------------------------------------------------
3710
3711
		/**
3712
		 * Check if plugin has any add-ons.
3713
		 *
3714
		 * @author Vova Feldman (@svovaf)
3715
		 * @since  1.0.5
3716
		 *
3717
		 * @return bool
3718
		 */
3719
		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...
3720
			$this->_logger->entrance();
3721
3722
			return ( $this->_has_addons || false !== $this->get_addons() );
3723
		}
3724
3725
		/**
3726
		 * Check if plugin can work in anonymous mode.
3727
		 *
3728
		 * @author Vova Feldman (@svovaf)
3729
		 * @since  1.0.9
3730
		 *
3731
		 * @return bool
3732
		 */
3733
		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...
3734
			return $this->_enable_anonymous;
3735
		}
3736
3737
		/**
3738
		 * Check if feature supported with current site's plan.
3739
		 *
3740
		 * @author Vova Feldman (@svovaf)
3741
		 * @since  1.0.1
3742
		 *
3743
		 * @todo   IMPLEMENT
3744
		 *
3745
		 * @param number $feature_id
3746
		 *
3747
		 * @throws Exception
3748
		 */
3749
		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...
3750
			throw new Exception( 'not implemented' );
3751
		}
3752
3753
		/**
3754
		 * @author Vova Feldman (@svovaf)
3755
		 * @since  1.0.1
3756
		 *
3757
		 * @return bool Is running in SSL/HTTPS
3758
		 */
3759
		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...
3760
			return WP_FS__IS_HTTPS;
3761
		}
3762
3763
		/**
3764
		 * @author Vova Feldman (@svovaf)
3765
		 * @since  1.0.9
3766
		 *
3767
		 * @return bool Is running in AJAX call.
3768
		 *
3769
		 * @link   http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax
3770
		 */
3771
		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...
3772
			return ( defined( 'DOING_AJAX' ) && DOING_AJAX );
3773
		}
3774
3775
		/**
3776
		 * Check if running in HTTPS and if site's plan matching the specified plan.
3777
		 *
3778
		 * @param string $plan
3779
		 * @param bool   $exact
3780
		 *
3781
		 * @return bool
3782
		 */
3783
		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...
3784
			return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) );
3785
		}
3786
3787
		/**
3788
		 * Construct plugin's settings page URL.
3789
		 *
3790
		 * @author Vova Feldman (@svovaf)
3791
		 * @since  1.0.4
3792
		 *
3793
		 * @param string $page
3794
		 * @param array  $params
3795
		 *
3796
		 * @return string
3797
		 */
3798
		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...
3799
			if ( empty( $page ) && ! $this->_menu->is_top_level() ) {
3800
				// If not a Top-Level menu and asking for main settings page,
3801
				// then try to replicate plugin's main setting original page URL.
3802
				switch ( $this->_menu->get_type() ) {
3803
					case 'tools':
3804
						return add_query_arg( array(
3805
							'page' => $this->_menu->get_raw_slug(),
3806
						), admin_url( 'tools.php' ) );
3807
					case 'settings':
3808
						return add_query_arg( array(
3809
							'page' => $this->_menu->get_raw_slug(),
3810
						), admin_url( 'options-general.php' ) );
3811
				}
3812
			}
3813
3814
			if ( $this->_menu->is_cpt() ) {
3815
				if ( empty( $page ) && $this->is_activation_mode() ) {
3816
					return add_query_arg( array_merge( $params, array(
3817
						'page' => $this->_menu->get_raw_slug()
3818
					) ), admin_url( 'admin.php', 'admin' ) );
3819
				} else {
3820
					if ( ! empty( $page ) ) {
3821
						$params['page'] = trim( "{$this->_menu->get_raw_slug()}-{$page}", '-' );
3822
					}
3823
3824
					return add_query_arg( array_merge( $params, array(
3825
						'post_type' => $this->_menu->get_raw_slug(),
3826
					) ), admin_url( 'edit.php', 'admin' ) );
3827
				}
3828
			} else if ( false === strpos( $this->_menu->get_raw_slug(), '.php?' ) ) {
3829
				return add_query_arg( array_merge( $params, array(
3830
					'page' => trim( "{$this->_menu->get_raw_slug()}-{$page}", '-' )
3831
				) ), admin_url( 'admin.php', 'admin' ) );
3832
			} else {
3833
				return add_query_arg( array_merge( $params, array(
3834
					'page' => trim( "{$this->_slug}-{$page}", '-' )
3835
				) ), admin_url( 'admin.php', 'admin' ) );
3836
			}
3837
		}
3838
3839
3840
		/**
3841
		 * Plugin's account URL.
3842
		 *
3843
		 * @author Vova Feldman (@svovaf)
3844
		 * @since  1.0.4
3845
		 *
3846
		 * @param bool|string $action
3847
		 * @param array       $params
3848
		 *
3849
		 * @param bool        $add_action_nonce
3850
		 *
3851
		 * @return string
3852
		 */
3853
		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...
3854
			if ( is_string( $action ) ) {
3855
				$params['fs_action'] = $action;
3856
			}
3857
3858
			self::require_pluggable_essentials();
3859
3860
			return ( $add_action_nonce && is_string( $action ) ) ?
3861
				wp_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) :
3862
				$this->_get_admin_page_url( 'account', $params );
3863
		}
3864
3865
		/**
3866
		 * Plugin's account URL.
3867
		 *
3868
		 * @author Vova Feldman (@svovaf)
3869
		 * @since  1.0.4
3870
		 *
3871
		 * @param bool|string $topic
3872
		 * @param bool|string $message
3873
		 *
3874
		 * @return string
3875
		 */
3876
		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...
3877
			$params = array();
3878
			if ( is_string( $topic ) ) {
3879
				$params['topic'] = $topic;
3880
			}
3881
			if ( is_string( $message ) ) {
3882
				$params['message'] = $message;
3883
			}
3884
3885
			if ( $this->is_addon() ) {
3886
				$params['addon_id'] = $this->get_id();
3887
3888
				return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params );
3889
			} else {
3890
				return $this->_get_admin_page_url( 'contact', $params );
3891
			}
3892
		}
3893
3894
		/**
3895
		 * Add-on direct info URL.
3896
		 *
3897
		 * @author Vova Feldman (@svovaf)
3898
		 * @since  1.1.0
3899
		 *
3900
		 * @param string $slug
3901
		 *
3902
		 * @return string
3903
		 */
3904
		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...
3905
			return $this->_get_admin_page_url( 'addons', array(
3906
				'slug' => $slug
3907
			) );
3908
		}
3909
3910
		/* Logger
3911
		------------------------------------------------------------------------------------------------------------------*/
3912
		/**
3913
		 * @param string $id
3914
		 * @param bool   $prefix_slug
3915
		 *
3916
		 * @return FS_Logger
3917
		 */
3918
		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...
3919
			return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id );
3920
		}
3921
3922
		/**
3923
		 * @param      $id
3924
		 * @param bool $load_options
3925
		 * @param bool $prefix_slug
3926
		 *
3927
		 * @return FS_Option_Manager
3928
		 */
3929
		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...
3930
			return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options );
3931
		}
3932
3933
		/* Security
3934
		------------------------------------------------------------------------------------------------------------------*/
3935
		private function _encrypt( $str ) {
3936
			if ( is_null( $str ) ) {
3937
				return null;
3938
			}
3939
3940
			return base64_encode( $str );
3941
		}
3942
3943
		private function _decrypt( $str ) {
3944
			if ( is_null( $str ) ) {
3945
				return null;
3946
			}
3947
3948
			return base64_decode( $str );
3949
		}
3950
3951
		/**
3952
		 * @author Vova Feldman (@svovaf)
3953
		 * @since  1.0.5
3954
		 *
3955
		 * @param FS_Entity $entity
3956
		 *
3957
		 * @return FS_Entity Return an encrypted clone entity.
3958
		 */
3959
		private function _encrypt_entity( FS_Entity $entity ) {
3960
			$clone = clone $entity;
3961
			$props = get_object_vars( $entity );
3962
3963
			foreach ( $props as $key => $val ) {
3964
				$clone->{$key} = $this->_encrypt( $val );
3965
			}
3966
3967
			return $clone;
3968
		}
3969
3970
		/**
3971
		 * @author Vova Feldman (@svovaf)
3972
		 * @since  1.0.5
3973
		 *
3974
		 * @param FS_Entity $entity
3975
		 *
3976
		 * @return FS_Entity Return an decrypted clone entity.
3977
		 */
3978
		private function _decrypt_entity( FS_Entity $entity ) {
3979
			$clone = clone $entity;
3980
			$props = get_object_vars( $entity );
3981
3982
			foreach ( $props as $key => $val ) {
3983
				$clone->{$key} = $this->_decrypt( $val );
3984
			}
3985
3986
			return $clone;
3987
		}
3988
3989
		/**
3990
		 * Tries to activate account based on POST params.
3991
		 *
3992
		 * @author Vova Feldman (@svovaf)
3993
		 * @since  1.0.2
3994
		 */
3995
		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...
3996
			if ( $this->is_registered() ) {
3997
				// Already activated.
3998
				return;
3999
			}
4000
4001
			$this->_clean_admin_content_section();
4002
4003
			if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) {
4004
//				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...
4005
4006
				// Verify matching plugin details.
4007
				if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) {
4008
					return;
4009
				}
4010
4011
				$user              = new FS_User();
4012
				$user->id          = fs_request_get( 'user_id' );
4013
				$user->public_key  = fs_request_get( 'user_public_key' );
4014
				$user->secret_key  = fs_request_get( 'user_secret_key' );
4015
				$user->email       = fs_request_get( 'user_email' );
4016
				$user->first       = fs_request_get( 'user_first' );
4017
				$user->last        = fs_request_get( 'user_last' );
4018
				$user->is_verified = fs_request_get_bool( 'user_is_verified' );
4019
4020
				$site              = new FS_Site();
4021
				$site->id          = fs_request_get( 'install_id' );
4022
				$site->public_key  = fs_request_get( 'install_public_key' );
4023
				$site->secret_key  = fs_request_get( 'install_secret_key' );
4024
				$site->plan->id    = fs_request_get( 'plan_id' );
4025
				$site->plan->title = fs_request_get( 'plan_title' );
4026
				$site->plan->name  = fs_request_get( 'plan_name' );
4027
4028
				$plans      = array();
4029
				$plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) );
4030
				foreach ( $plans_data as $p ) {
4031
					$plans[] = new FS_Plugin_Plan( $p );
4032
				}
4033
4034
				$this->_set_account( $user, $site, $plans );
4035
4036
				// Reload the page with the keys.
4037
				if ( fs_redirect( $this->_get_admin_page_url() ) ) {
4038
					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...
4039
				}
4040
			}
4041
		}
4042
4043
		/**
4044
		 * @author Vova Feldman (@svovaf)
4045
		 * @since  1.0.7
4046
		 *
4047
		 * @param string $email
4048
		 *
4049
		 * @return FS_User|bool
4050
		 */
4051
		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...
4052
			self::$_static_logger->entrance();
4053
4054
			$email = trim( strtolower( $email ) );
4055
			$users = self::get_all_users();
4056
			if ( is_array( $users ) ) {
4057
				foreach ( $users as $u ) {
4058
					if ( $email === trim( strtolower( $u->email ) ) ) {
4059
						return $u;
4060
					}
4061
				}
4062
			}
4063
4064
			return false;
4065
		}
4066
4067
		#region Account (Loading, Updates & Activation) ------------------------------------------------------------------
4068
4069
		/***
4070
		 * Load account information (user + site).
4071
		 *
4072
		 * @author Vova Feldman (@svovaf)
4073
		 * @since  1.0.1
4074
		 */
4075
		private function _load_account() {
4076
			$this->_logger->entrance();
4077
4078
			$this->do_action( 'before_account_load' );
4079
4080
			$sites    = self::get_all_sites();
4081
			$users    = self::get_all_users();
4082
			$plans    = self::get_all_plans();
4083
			$licenses = self::get_all_licenses();
4084
4085
			if ( $this->_logger->is_on() && is_admin() ) {
4086
				$this->_logger->log( 'sites = ' . var_export( $sites, true ) );
4087
				$this->_logger->log( 'users = ' . var_export( $users, true ) );
4088
				$this->_logger->log( 'plans = ' . var_export( $plans, true ) );
4089
				$this->_logger->log( 'licenses = ' . var_export( $licenses, true ) );
4090
			}
4091
4092
			$site = isset( $sites[ $this->_slug ] ) ? $sites[ $this->_slug ] : false;
4093
4094
			if ( is_object( $site ) &&
4095
			     is_numeric( $site->id ) &&
4096
			     is_numeric( $site->user_id ) &&
4097
			     is_object( $site->plan )
4098
			) {
4099
				// Load site.
4100
				$this->_site       = clone $site;
4101
				$this->_site->plan = $this->_decrypt_entity( $this->_site->plan );
4102
4103
				// Load relevant user.
4104
				$this->_user = clone $users[ $this->_site->user_id ];
4105
4106
				// Load plans.
4107
				$this->_plans = $plans[ $this->_slug ];
4108
				if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) {
4109
					$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...
4110
				} else {
4111
					for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
4112
						if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) {
4113
							$this->_plans[ $i ] = $this->_decrypt_entity( $this->_plans[ $i ] );
4114
						} else {
4115
							unset( $this->_plans[ $i ] );
4116
						}
4117
					}
4118
				}
4119
4120
				// Load licenses.
4121
				$this->_licenses = array();
4122
				if ( is_array( $licenses ) &&
4123
				     isset( $licenses[ $this->_slug ] ) &&
4124
				     isset( $licenses[ $this->_slug ][ $this->_user->id ] )
4125
				) {
4126
					$this->_licenses = $licenses[ $this->_slug ][ $this->_user->id ];
4127
				}
4128
4129
				$this->_license = $this->_get_license_by_id( $this->_site->license_id );
4130
4131
				if ( $this->_site->version != $this->get_plugin_version() ) {
4132
					// If stored install version is different than current installed plugin version,
4133
					// then update plugin version event.
4134
					$this->update_plugin_version_event();
4135
				}
4136
			}
4137
4138
			$this->_register_account_hooks();
4139
		}
4140
4141
		/**
4142
		 * @author Vova Feldman (@svovaf)
4143
		 * @since  1.0.1
4144
		 *
4145
		 * @param FS_User    $user
4146
		 * @param FS_Site    $site
4147
		 * @param bool|array $plans
4148
		 */
4149
		private function _set_account( FS_User $user, FS_Site $site, $plans = false ) {
4150
			$site->slug    = $this->_slug;
4151
			$site->user_id = $user->id;
4152
4153
			$this->_site = $site;
4154
			$this->_user = $user;
4155
			if ( false !== $plans ) {
4156
				$this->_plans = $plans;
4157
			}
4158
4159
			$this->send_install_update( array(), true );
4160
4161
			$this->_store_account();
4162
4163
		}
4164
4165
		/**
4166
		 * Set user and site identities.
4167
		 *
4168
		 * @author Vova Feldman (@svovaf)
4169
		 * @since  1.0.9
4170
		 *
4171
		 * @param FS_User $user
4172
		 * @param FS_Site $site
4173
		 * @param bool    $redirect
4174
		 *
4175
		 * @return bool False if account already set.
4176
		 */
4177
		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...
4178
			$this->_user = $user;
4179
			$this->_site = $site;
4180
			$this->_enrich_site_plan( false );
4181
4182
			$this->_set_account( $user, $site );
4183
			$this->_sync_plans();
4184
4185
			if ( $this->is_trial() ) {
4186
				// Store trial plan information.
4187
				$this->_enrich_site_trial_plan( true );
4188
			}
4189
4190
			$this->do_action( 'after_account_connection', $user, $site );
4191
4192
			if ( is_numeric( $site->license_id ) ) {
4193
				$this->_license = $this->_get_license_by_id( $site->license_id );
4194
			}
4195
4196
			if ( $this->is_pending_activation() ) {
4197
				// Remove pending activation sticky notice (if still exist).
4198
				$this->_admin_notices->remove_sticky( 'activation_pending' );
4199
4200
				// Remove plugin from pending activation mode.
4201
				unset( $this->_storage->is_pending_activation );
4202
4203
				if ( ! $this->is_paying() ) {
4204
					$this->_admin_notices->add_sticky(
4205
						sprintf( __fs( 'plugin-x-activation-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
4206
						'activation_complete'
4207
					);
4208
				}
4209
			}
4210
4211
			if ( $this->is_paying() && ! $this->is_premium() ) {
4212
				$this->_admin_notices->add_sticky(
4213
					sprintf(
4214
						__fs( 'activation-with-plan-x-message' ),
4215
						$this->_site->plan->title
4216
					) . ' ' . $this->_get_latest_download_link( sprintf(
4217
						__fs( 'download-latest-x-version' ),
4218
						$this->_site->plan->title
4219
					) ),
4220
					'plan_upgraded',
4221
					__fs( 'yee-haw' ) . '!'
4222
				);
4223
			}
4224
4225
			$plugin_id = fs_request_get( 'plugin_id', false );
4226
4227
			// Store activation time ONLY for plugins (not add-ons).
4228
			if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) {
4229
				$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...
4230
			}
4231
4232
			if ( is_numeric( $plugin_id ) ) {
4233
				if ( $plugin_id != $this->_plugin->id ) {
4234
					// Add-on was installed - sync license right after install.
4235
					if ( $redirect && fs_redirect( fs_nonce_url( $this->_get_admin_page_url(
4236
							'account',
4237
							array(
4238
								'fs_action' => $this->_slug . '_sync_license',
4239
								'plugin_id' => $plugin_id
4240
							)
4241
						), $this->_slug . '_sync_license' ) )
4242
					) {
4243
						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...
4244
					}
4245
4246
				}
4247
			} else {
4248
				// Reload the page with the keys.
4249
				if ( $redirect && fs_redirect( $this->get_after_activation_url( 'after_connect_url' ) ) ) {
4250
					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...
4251
				}
4252
			}
4253
		}
4254
4255
		/**
4256
		 * Install plugin with new user information after approval.
4257
		 *
4258
		 * @author Vova Feldman (@svovaf)
4259
		 * @since  1.0.7
4260
		 */
4261
		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...
4262
			if ( $this->is_registered() ) {
4263
				return;
4264
			}
4265
4266
			if ( fs_request_is_action( $this->_slug . '_activate_new' ) ) {
4267
//				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...
4268
4269
				if ( fs_request_has( 'user_secret_key' ) ) {
4270
					$user             = new FS_User();
4271
					$user->id         = fs_request_get( 'user_id' );
4272
					$user->public_key = fs_request_get( 'user_public_key' );
4273
					$user->secret_key = fs_request_get( 'user_secret_key' );
4274
4275
					$this->_user = $user;
4276
					$user_result = $this->get_api_user_scope()->get();
4277
					$user        = new FS_User( $user_result );
4278
					$this->_user = $user;
4279
4280
					$site             = new FS_Site();
4281
					$site->id         = fs_request_get( 'install_id' );
4282
					$site->public_key = fs_request_get( 'install_public_key' );
4283
					$site->secret_key = fs_request_get( 'install_secret_key' );
4284
4285
					$this->_site = $site;
4286
					$site_result = $this->get_api_site_scope()->get();
4287
					$site        = new FS_Site( $site_result );
4288
					$this->_site = $site;
4289
4290
					$this->setup_account( $this->_user, $this->_site );
4291
				} else if ( fs_request_has( 'pending_activation' ) ) {
4292
					// Install must be activated via email since
4293
					// user with the same email already exist.
4294
					$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...
4295
					$this->_add_pending_activation_notice( fs_request_get( 'user_email' ) );
4296
4297
					// Reload the page with with pending activation message.
4298
					if ( fs_redirect( $this->get_after_activation_url( 'after_pending_connect_url' ) ) ) {
4299
						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...
4300
					}
4301
				}
4302
			}
4303
		}
4304
4305
		/**
4306
		 * Install plugin with current logged WP user info.
4307
		 *
4308
		 * @author Vova Feldman (@svovaf)
4309
		 * @since  1.0.7
4310
		 */
4311
		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...
4312
			if ( $this->is_registered() ) {
4313
				return;
4314
			}
4315
4316
			if ( fs_request_is_action( $this->_slug . '_activate_existing' ) && fs_request_is_post() ) {
4317
//				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...
4318
				// Get current logged WP user.
4319
				$current_user = wp_get_current_user();
4320
4321
				// Find the relevant FS user by the email.
4322
				$user = self::_get_user_by_email( $current_user->user_email );
4323
4324
				// We have to set the user before getting user scope API handler.
4325
				$this->_user = $user;
4326
4327
				// Install the plugin.
4328
				$install = $this->get_api_user_scope()->call(
4329
					"/plugins/{$this->get_id()}/installs.json",
4330
					'post',
4331
					$this->get_install_data_for_api( array(
4332
						'uid' => $this->get_anonymous_id(),
4333
					) )
4334
				);
4335
4336
				if ( isset( $install->error ) ) {
4337
					$this->_admin_notices->add(
4338
						sprintf( __fs( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
4339
						__fs( 'contact-us-with-error-message' ) . ' ' . '<b>' . $install->error->message . '</b>',
4340
						__fs( 'oops' ) . '...',
4341
						'error'
4342
					);
4343
4344
					return;
4345
				}
4346
4347
				$site        = new FS_Site( $install );
4348
				$this->_site = $site;
4349
//				$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...
4350
4351
//				$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...
4352
//				$this->_sync_plans();
4353
4354
				$this->setup_account( $this->_user, $this->_site );
4355
			}
4356
		}
4357
4358
		/**
4359
		 * Tries to activate add-on account based on parent plugin info.
4360
		 *
4361
		 * @author Vova Feldman (@svovaf)
4362
		 * @since  1.0.6
4363
		 *
4364
		 * @param Freemius $parent_fs
4365
		 */
4366
		private function _activate_addon_account( Freemius $parent_fs ) {
4367
			if ( $this->is_registered() ) {
4368
				// Already activated.
4369
				return;
4370
			}
4371
4372
			// Activate add-on with parent plugin credentials.
4373
			$addon_install = $parent_fs->get_api_site_scope()->call(
4374
				"/addons/{$this->_plugin->id}/installs.json",
4375
				'post',
4376
				$this->get_install_data_for_api( array(
4377
					'uid' => $this->get_anonymous_id(),
4378
				) )
4379
			);
4380
4381
			if ( isset( $addon_install->error ) ) {
4382
				$this->_admin_notices->add(
4383
					sprintf( __fs( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
4384
					__fs( 'contact-us-with-error-message' ) . ' ' . '<b>' . $addon_install->error->message . '</b>',
4385
					__fs( 'oops' ) . '...',
4386
					'error'
4387
				);
4388
4389
				return;
4390
			}
4391
4392
			// First of all, set site info - otherwise we won't
4393
			// be able to invoke API calls.
4394
			$this->_site = new FS_Site( $addon_install );
4395
4396
			// Sync add-on plans.
4397
			$this->_sync_plans();
4398
4399
			// Get site's current plan.
4400
			$this->_site->plan = $this->_get_plan_by_id( $this->_site->plan->id );
4401
4402
			// Get user information based on parent's plugin.
4403
			$user = $parent_fs->get_user();
4404
4405
			$this->_set_account( $user, $this->_site );
4406
4407
			// Sync licenses.
4408
			$this->_sync_licenses();
4409
4410
			// Try to activate premium license.
4411
			$this->_activate_license( true );
4412
		}
4413
4414
		#endregion ------------------------------------------------------------------
4415
4416
		#region Admin Menu Items ------------------------------------------------------------------
4417
4418
		private $_menu_items = array();
4419
4420
		/**
4421
		 * @author Vova Feldman (@svovaf)
4422
		 * @since  1.0.7
4423
		 *
4424
		 * @return string
4425
		 */
4426
		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...
4427
			return $this->_menu->get_slug();
4428
		}
4429
4430
		/**
4431
		 * @author Vova Feldman (@svovaf)
4432
		 * @since  1.0.9
4433
		 */
4434
		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...
4435
			if ( ! $this->is_on() ) {
4436
				return;
4437
			}
4438
4439
			if ( ! $this->has_api_connectivity() && ! $this->enable_anonymous() ) {
4440
				$this->_menu->remove_menu_item();
4441
			} else {
4442
				$this->add_submenu_items();
4443
				$this->add_menu_action();
4444
			}
4445
		}
4446
4447
		/**
4448
		 * Admin dashboard menu items modifications.
4449
		 *
4450
		 * NOTE: admin_menu action executed before admin_init.
4451
		 *
4452
		 * @author Vova Feldman (@svovaf)
4453
		 * @since  1.0.7
4454
		 *
4455
		 */
4456
		private function add_menu_action() {
4457
			if ( $this->is_activation_mode() ) {
4458
				$this->override_plugin_menu_with_activation();
4459
			} else {
4460
				// If not registered try to install user.
4461
				if ( ! $this->is_registered() &&
4462
				     fs_request_is_action( $this->_slug . '_activate_new' )
4463
				) {
4464
					$this->_install_with_new_user();
4465
				}
4466
			}
4467
		}
4468
4469
		/**
4470
		 * @author Vova Feldman (@svovaf)
4471
		 * @since  1.0.1
4472
		 *
4473
		 * @return string
4474
		 */
4475
		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...
4476
			$this->_logger->entrance();
4477
4478
			$page = strtolower( isset( $_REQUEST['page'] ) ? $_REQUEST['page'] : '' );
4479
4480
			$this->_logger->log( 'page = ' . $page );
4481
4482
			foreach ( $this->_menu_items as $priority => $items ) {
4483
				foreach ( $items as $item ) {
4484
					if ( isset( $item['url'] ) ) {
4485
						if ( $page === $item['menu_slug'] ) {
4486
							$this->_logger->log( 'Redirecting to ' . $item['url'] );
4487
4488
							fs_redirect( $item['url'] );
4489
						}
4490
					}
4491
				}
4492
			}
4493
		}
4494
4495
		/**
4496
		 * Remove plugin's all admin menu items & pages, and replace with activation page.
4497
		 *
4498
		 * @author Vova Feldman (@svovaf)
4499
		 * @since  1.0.1
4500
		 */
4501
		private function override_plugin_menu_with_activation() {
4502
			$this->_logger->entrance();
4503
4504
			$hook = false;
4505
4506
			if ( $this->_menu->is_top_level() ) {
4507
				$menu = $this->_menu->remove_menu_item();
4508
4509
				if ( false !== $menu ) {
4510
					// Override menu action.
4511
					$hook = add_menu_page(
4512
						$menu['menu'][3],
4513
						$menu['menu'][0],
4514
						'manage_options',
4515
						$this->_menu->get_slug(),
4516
						array( &$this, '_connect_page_render' ),
4517
						$menu['menu'][6],
4518
						$menu['position']
4519
					);
4520
				}
4521
			} else {
4522
				if ( $this->_menu->has_custom_parent() ) {
4523
					$menus = array( $this->_menu->get_parent_slug() );
4524
4525
					if ( $this->_menu->is_override_exact() ) {
4526
4527
						// Make sure the current page is matching the activation page.
4528
						$activation_url = strtolower( $this->get_activation_url() );
4529
						$request_url    = strtolower( $_SERVER['REQUEST_URI'] );
4530
4531
						if ( parse_url( $activation_url, PHP_URL_PATH ) !== parse_url( $request_url, PHP_URL_PATH ) ) {
4532
							// Different path - DO NOT OVERRIDE PAGE.
4533
							return;
4534
						}
4535
4536
						$activation_url_params = array();
4537
						parse_str( parse_url( $activation_url, PHP_URL_QUERY ), $activation_url_params );
4538
4539
						$request_url_params = array();
4540
						parse_str( parse_url( $request_url, PHP_URL_QUERY ), $request_url_params );
4541
4542
4543
						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...
4544
							if ( ! isset( $request_url_params[ $key ] ) || $val != $request_url_params[ $key ] ) {
4545
								// Not matching query string - DO NOT OVERRIDE PAGE.
4546
								return;
4547
							}
4548
						}
4549
					}
4550
				} else {
4551
					$menus = array(
4552
						'tools.php',
4553
						'options-general.php',
4554
					);
4555
				}
4556
4557
				foreach ( $menus as $parent_slug ) {
4558
					$hook = $this->_menu->override_submenu_action(
4559
						$parent_slug,
4560
						$this->_menu->get_raw_slug(),
4561
						array( &$this, '_connect_page_render' )
4562
					);
4563
4564
					if ( false !== $hook ) {
4565
						// Found plugin's submenu item.
4566
						break;
4567
					}
4568
				}
4569
			}
4570
4571
			if ( $this->_menu->is_activation_page() ) {
4572
				// Clean admin page from distracting content.
4573
				$this->_clean_admin_content_section();
4574
			}
4575
4576
			if ( false !== $hook ) {
4577
				if ( fs_request_is_action( $this->_slug . '_activate_existing' ) ) {
4578
					add_action( "load-$hook", array( &$this, '_install_with_current_user' ) );
4579
				} else if ( fs_request_is_action( $this->_slug . '_activate_new' ) ) {
4580
					add_action( "load-$hook", array( &$this, '_install_with_new_user' ) );
4581
				}
4582
			}
4583
		}
4584
4585
4586
		/**
4587
		 * Add default Freemius menu items.
4588
		 *
4589
		 * @author Vova Feldman (@svovaf)
4590
		 * @since  1.0.0
4591
		 */
4592
		private function add_submenu_items() {
4593
			$this->_logger->entrance();
4594
4595
			$this->do_action( 'before_admin_menu_init' );
4596
4597
			if ( ! $this->is_addon() ) {
4598
				if ( $this->is_registered() || $this->is_anonymous() ) {
4599
					if ( $this->is_registered() ) {
4600
						// Add user account page.
4601
						$this->add_submenu_item(
4602
							__fs( 'account' ),
4603
							array( &$this, '_account_page_render' ),
4604
							$this->get_plugin_name() . ' &ndash; ' . __fs( 'account' ),
4605
							'manage_options',
4606
							'account',
4607
							array( &$this, '_account_page_load' ),
4608
							10,
4609
							$this->_menu->is_submenu_item_visible( 'account' )
4610
						);
4611
					}
4612
4613
					// Add contact page.
4614
					$this->add_submenu_item(
4615
						__fs( 'contact-us' ),
4616
						array( &$this, '_contact_page_render' ),
4617
						$this->get_plugin_name() . ' &ndash; ' . __fs( 'contact-us' ),
4618
						'manage_options',
4619
						'contact',
4620
						array( &$this, '_clean_admin_content_section' ),
4621
						10,
4622
						$this->_menu->is_submenu_item_visible( 'contact' )
4623
					);
4624
4625
					if ( $this->_has_addons() ) {
4626
						$this->add_submenu_item(
4627
							__fs( 'add-ons' ),
4628
							array( &$this, '_addons_page_render' ),
4629
							$this->get_plugin_name() . ' &ndash; ' . __fs( 'add-ons' ),
4630
							'manage_options',
4631
							'addons',
4632
							array( &$this, '_addons_page_load' ),
4633
							WP_FS__LOWEST_PRIORITY - 1,
4634
							$this->_menu->is_submenu_item_visible( 'addons' )
4635
						);
4636
					}
4637
4638
					// Add upgrade/pricing page.
4639
					$this->add_submenu_item(
4640
						( $this->is_paying() ? __fs( 'pricing' ) : __fs( 'upgrade' ) . '&nbsp;&nbsp;&#x27a4;' ),
4641
						array( &$this, '_pricing_page_render' ),
4642
						$this->get_plugin_name() . ' &ndash; ' . __fs( 'pricing' ),
4643
						'manage_options',
4644
						'pricing',
4645
						array( &$this, '_clean_admin_content_section' ),
4646
						WP_FS__LOWEST_PRIORITY,
4647
						// If user don't have paid plans, add pricing page
4648
						// to support add-ons checkout but don't add the submenu item.
4649
						$this->_menu->is_submenu_item_visible( 'pricing' ) && ( $this->has_paid_plan() || ( isset( $_GET['page'] ) && $this->_menu->get_slug( 'pricing' ) == $_GET['page'] ) )
4650
					);
4651
				}
4652
			}
4653
4654
			ksort( $this->_menu_items );
4655
4656
			foreach ( $this->_menu_items as $priority => $items ) {
4657
				foreach ( $items as $item ) {
4658
					if ( ! isset( $item['url'] ) ) {
4659
						$hook = add_submenu_page(
4660
							$item['show_submenu'] ?
4661
								( $this->is_addon() ?
4662
									$this->get_parent_instance()->_menu->get_top_level_menu_slug() :
4663
									$this->_menu->get_top_level_menu_slug() ) :
4664
								null,
4665
							$item['page_title'],
4666
							$item['menu_title'],
4667
							$item['capability'],
4668
							$item['menu_slug'],
4669
							$item['render_function']
4670
						);
4671
4672
						if ( false !== $item['before_render_function'] ) {
4673
							add_action( "load-$hook", $item['before_render_function'] );
4674
						}
4675
					} else {
4676
						add_submenu_page(
4677
							$this->is_addon() ?
4678
								$this->get_parent_instance()->_menu->get_top_level_menu_slug() :
4679
								$this->_menu->get_top_level_menu_slug(),
4680
							$item['page_title'],
4681
							$item['menu_title'],
4682
							$item['capability'],
4683
							$item['menu_slug'],
4684
							array( $this, '' )
4685
						);
4686
					}
4687
				}
4688
			}
4689
		}
4690
4691
		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...
4692
			if ( ! $this->is_on() ) {
4693
				return;
4694
			}
4695
4696
			if ( $this->is_registered() ) {
4697
				if ( $this->_menu->is_submenu_item_visible( 'support' ) ) {
4698
					$this->add_submenu_link_item(
4699
						__fs( 'support-forum' ),
4700
						'https://wordpress.org/support/plugin/' . $this->_slug,
4701
						'wp-support-forum',
4702
						'read',
4703
						50
4704
					);
4705
				}
4706
			}
4707
		}
4708
4709
		/**
4710
		 * @author Vova Feldman (@svovaf)
4711
		 * @since  1.0.1
4712
		 *
4713
		 * @param string        $menu_title
4714
		 * @param callable      $render_function
4715
		 * @param bool|string   $page_title
4716
		 * @param string        $capability
4717
		 * @param bool|string   $menu_slug
4718
		 * @param bool|callable $before_render_function
4719
		 * @param int           $priority
4720
		 * @param bool          $show_submenu
4721
		 */
4722
		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...
4723
			$menu_title,
4724
			$render_function,
4725
			$page_title = false,
4726
			$capability = 'manage_options',
4727
			$menu_slug = false,
4728
			$before_render_function = false,
4729
			$priority = 10,
4730
			$show_submenu = true
4731
		) {
4732
			$this->_logger->entrance( 'Title = ' . $menu_title );
4733
4734
			if ( $this->is_addon() ) {
4735
				$parent_fs = $this->get_parent_instance();
4736
4737
				if ( is_object( $parent_fs ) ) {
4738
					$parent_fs->add_submenu_item(
4739
						$menu_title,
4740
						$render_function,
4741
						$page_title,
4742
						$capability,
4743
						$menu_slug,
4744
						$before_render_function,
4745
						$priority,
4746
						$show_submenu
4747
					);
4748
4749
					return;
4750
				}
4751
			}
4752
4753
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
4754
				$this->_menu_items[ $priority ] = array();
4755
			}
4756
4757
			$this->_menu_items[ $priority ][] = array(
4758
				'page_title'             => is_string( $page_title ) ? $page_title : $menu_title,
4759
				'menu_title'             => $menu_title,
4760
				'capability'             => $capability,
4761
				'menu_slug'              => $this->_menu->get_slug( is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ) ),
4762
				'render_function'        => $render_function,
4763
				'before_render_function' => $before_render_function,
4764
				'show_submenu'           => $show_submenu,
4765
			);
4766
		}
4767
4768
		/**
4769
		 * @author Vova Feldman (@svovaf)
4770
		 * @since  1.0.1
4771
		 *
4772
		 * @param string $menu_title
4773
		 * @param string $url
4774
		 * @param bool   $menu_slug
4775
		 * @param string $capability
4776
		 * @param int    $priority
4777
		 *
4778
		 */
4779
		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...
4780
			$menu_title,
4781
			$url,
4782
			$menu_slug = false,
4783
			$capability = 'read',
4784
			$priority = 10
4785
		) {
4786
			$this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url );
4787
4788
			if ( $this->is_addon() ) {
4789
				$parent_fs = $this->get_parent_instance();
4790
4791
				if ( is_object( $parent_fs ) ) {
4792
					$parent_fs->add_submenu_link_item(
4793
						$menu_title,
4794
						$url,
4795
						$menu_slug,
4796
						$capability,
4797
						$priority
4798
					);
4799
4800
					return;
4801
				}
4802
			}
4803
4804
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
4805
				$this->_menu_items[ $priority ] = array();
4806
			}
4807
4808
			$this->_menu_items[ $priority ][] = array(
4809
				'menu_title'             => $menu_title,
4810
				'capability'             => $capability,
4811
				'menu_slug'              => $this->_menu->get_slug( is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ) ),
4812
				'url'                    => $url,
4813
				'page_title'             => $menu_title,
4814
				'render_function'        => 'fs_dummy',
4815
				'before_render_function' => '',
4816
			);
4817
		}
4818
4819
		#endregion ------------------------------------------------------------------
4820
4821
		/* Actions / Hooks / Filters
4822
		------------------------------------------------------------------------------------------------------------------*/
4823
		/**
4824
		 * Do action, specific for the current context plugin.
4825
		 *
4826
		 * @author Vova Feldman (@svovaf)
4827
		 * @since  1.0.1
4828
		 *
4829
		 * @param string $tag     The name of the action to be executed.
4830
		 * @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...
4831
		 *                        functions hooked to the action. Default empty.
4832
		 *
4833
		 * @uses   do_action()
4834
		 */
4835
		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...
4836
			$this->_logger->entrance( $tag );
4837
4838
			$args = func_get_args();
4839
4840
			call_user_func_array( 'do_action', array_merge(
4841
					array( 'fs_' . $tag . '_' . $this->_slug ),
4842
					array_slice( $args, 1 ) )
4843
			);
4844
		}
4845
4846
		/**
4847
		 * Add action, specific for the current context plugin.
4848
		 *
4849
		 * @author Vova Feldman (@svovaf)
4850
		 * @since  1.0.1
4851
		 *
4852
		 * @param string   $tag
4853
		 * @param callable $function_to_add
4854
		 * @param int      $priority
4855
		 * @param int      $accepted_args
4856
		 *
4857
		 * @uses   add_action()
4858
		 */
4859
		function add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4860
			$this->_logger->entrance( $tag );
4861
4862
			add_action( 'fs_' . $tag . '_' . $this->_slug, $function_to_add, $priority, $accepted_args );
4863
		}
4864
4865
		/**
4866
		 * Apply filter, specific for the current context plugin.
4867
		 *
4868
		 * @author Vova Feldman (@svovaf)
4869
		 * @since  1.0.9
4870
		 *
4871
		 * @param string $tag   The name of the filter hook.
4872
		 * @param mixed  $value The value on which the filters hooked to `$tag` are applied on.
4873
		 *
4874
		 * @return mixed The filtered value after all hooked functions are applied to it.
4875
		 *
4876
		 * @uses   apply_filters()
4877
		 */
4878
		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...
4879
			$this->_logger->entrance( $tag );
4880
4881
			$args = func_get_args();
4882
4883
			return call_user_func_array( 'apply_filters', array_merge(
4884
					array( 'fs_' . $tag . '_' . $this->_slug ),
4885
					array_slice( $args, 1 ) )
4886
			);
4887
		}
4888
4889
		/**
4890
		 * Add filter, specific for the current context plugin.
4891
		 *
4892
		 * @author Vova Feldman (@svovaf)
4893
		 * @since  1.0.9
4894
		 *
4895
		 * @param string   $tag
4896
		 * @param callable $function_to_add
4897
		 * @param int      $priority
4898
		 * @param int      $accepted_args
4899
		 *
4900
		 * @uses   add_filter()
4901
		 */
4902
		function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4903
			$this->_logger->entrance( $tag );
4904
4905
			add_filter( 'fs_' . $tag . '_' . $this->_slug, $function_to_add, $priority, $accepted_args );
4906
		}
4907
4908
		/* Activation
4909
		------------------------------------------------------------------------------------------------------------------*/
4910
		/**
4911
		 * Render activation/sign-up page.
4912
		 *
4913
		 * @author Vova Feldman (@svovaf)
4914
		 * @since  1.0.1
4915
		 */
4916
		function _activation_page_render() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4917
			$this->_logger->entrance();
4918
4919
			$vars = array( 'slug' => $this->_slug );
4920
			fs_require_once_template( 'activation.php', $vars );
4921
		}
4922
4923
		/* Account Page
4924
		------------------------------------------------------------------------------------------------------------------*/
4925
		/**
4926
		 * Update site information.
4927
		 *
4928
		 * @author Vova Feldman (@svovaf)
4929
		 * @since  1.0.1
4930
		 *
4931
		 * @param bool $store Flush to Database if true.
4932
		 */
4933
		private function _store_site( $store = true ) {
4934
			$this->_logger->entrance();
4935
4936
			$encrypted_site       = clone $this->_site;
4937
			$encrypted_site->plan = $this->_encrypt_entity( $this->_site->plan );
4938
4939
			$sites                 = self::get_all_sites();
4940
			$sites[ $this->_slug ] = $encrypted_site;
4941
			self::$_accounts->set_option( 'sites', $sites, $store );
4942
		}
4943
4944
		/**
4945
		 * Update plugin's plans information.
4946
		 *
4947
		 * @author Vova Feldman (@svovaf)
4948
		 * @since  1.0.2
4949
		 *
4950
		 * @param bool $store Flush to Database if true.
4951
		 */
4952
		private function _store_plans( $store = true ) {
4953
			$this->_logger->entrance();
4954
4955
			$plans = self::get_all_plans();
4956
4957
			// Copy plans.
4958
			$encrypted_plans = array();
4959
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
4960
				$encrypted_plans[] = $this->_encrypt_entity( $this->_plans[ $i ] );
4961
			}
4962
4963
			$plans[ $this->_slug ] = $encrypted_plans;
4964
			self::$_accounts->set_option( 'plans', $plans, $store );
4965
		}
4966
4967
		/**
4968
		 * Update user's plugin licenses.
4969
		 *
4970
		 * @author Vova Feldman (@svovaf)
4971
		 * @since  1.0.5
4972
		 *
4973
		 * @param bool                $store
4974
		 * @param string|bool         $plugin_slug
4975
		 * @param FS_Plugin_License[] $licenses
4976
		 */
4977
		private function _store_licenses( $store = true, $plugin_slug = false, $licenses = array() ) {
4978
			$this->_logger->entrance();
4979
4980
			$all_licenses = self::get_all_licenses();
4981
4982
			if ( ! is_string( $plugin_slug ) ) {
4983
				$plugin_slug = $this->_slug;
4984
				$licenses    = $this->_licenses;
4985
			}
4986
4987
			if ( ! isset( $all_licenses[ $plugin_slug ] ) ) {
4988
				$all_licenses[ $plugin_slug ] = array();
4989
			}
4990
4991
			$all_licenses[ $plugin_slug ][ $this->_user->id ] = $licenses;
4992
4993
			self::$_accounts->set_option( 'licenses', $all_licenses, $store );
4994
		}
4995
4996
		/**
4997
		 * Update user information.
4998
		 *
4999
		 * @author Vova Feldman (@svovaf)
5000
		 * @since  1.0.1
5001
		 *
5002
		 * @param bool $store Flush to Database if true.
5003
		 */
5004
		private function _store_user( $store = true ) {
5005
			$this->_logger->entrance();
5006
5007
			$users                     = self::get_all_users();
5008
			$users[ $this->_user->id ] = $this->_user;
5009
			self::$_accounts->set_option( 'users', $users, $store );
5010
		}
5011
5012
		/**
5013
		 * Update new updates information.
5014
		 *
5015
		 * @author Vova Feldman (@svovaf)
5016
		 * @since  1.0.4
5017
		 *
5018
		 * @param FS_Plugin_Tag|null $update
5019
		 * @param bool               $store Flush to Database if true.
5020
		 * @param bool|number        $plugin_id
5021
		 */
5022
		private function _store_update( $update, $store = true, $plugin_id = false ) {
5023
			$this->_logger->entrance();
5024
5025
			if ( $update instanceof FS_Plugin_Tag ) {
5026
				$update->updated = time();
5027
			}
5028
5029
			if ( ! is_numeric( $plugin_id ) ) {
5030
				$plugin_id = $this->_plugin->id;
5031
			}
5032
5033
			$updates               = self::get_all_updates();
5034
			$updates[ $plugin_id ] = $update;
5035
			self::$_accounts->set_option( 'updates', $updates, $store );
5036
		}
5037
5038
		/**
5039
		 * Update new updates information.
5040
		 *
5041
		 * @author   Vova Feldman (@svovaf)
5042
		 * @since    1.0.6
5043
		 *
5044
		 * @param FS_Plugin[] $plugin_addons
5045
		 * @param bool        $store Flush to Database if true.
5046
		 */
5047
		private function _store_addons( $plugin_addons, $store = true ) {
5048
			$this->_logger->entrance();
5049
5050
			$addons                       = self::get_all_addons();
5051
			$addons[ $this->_plugin->id ] = $plugin_addons;
5052
			self::$_accounts->set_option( 'addons', $addons, $store );
5053
		}
5054
5055
		/**
5056
		 * Delete plugin's associated add-ons.
5057
		 *
5058
		 * @author   Vova Feldman (@svovaf)
5059
		 * @since    1.0.8
5060
		 *
5061
		 * @param bool $store
5062
		 *
5063
		 * @return bool
5064
		 */
5065
		private function _delete_account_addons( $store = true ) {
5066
			$all_addons = self::get_all_account_addons();
5067
5068
			if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) {
5069
				return false;
5070
			}
5071
5072
			unset( $all_addons[ $this->_plugin->id ] );
5073
5074
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
5075
5076
			return true;
5077
		}
5078
5079
		/**
5080
		 * Update account add-ons list.
5081
		 *
5082
		 * @author   Vova Feldman (@svovaf)
5083
		 * @since    1.0.6
5084
		 *
5085
		 * @param FS_Plugin[] $addons
5086
		 * @param bool        $store Flush to Database if true.
5087
		 */
5088
		private function _store_account_addons( $addons, $store = true ) {
5089
			$this->_logger->entrance();
5090
5091
			$all_addons                       = self::get_all_account_addons();
5092
			$all_addons[ $this->_plugin->id ] = $addons;
5093
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
5094
		}
5095
5096
		/**
5097
		 * Store account params in the Database.
5098
		 *
5099
		 * @author Vova Feldman (@svovaf)
5100
		 * @since  1.0.1
5101
		 */
5102
		private function _store_account() {
5103
			$this->_logger->entrance();
5104
5105
			$this->_store_site( false );
5106
			$this->_store_user( false );
5107
			$this->_store_plans( false );
5108
			$this->_store_licenses( false );
5109
5110
			self::$_accounts->store();
5111
		}
5112
5113
		/**
5114
		 * Sync user's information.
5115
		 *
5116
		 * @author Vova Feldman (@svovaf)
5117
		 * @since  1.0.3
5118
		 * @uses   FS_Api
5119
		 */
5120
		private function _handle_account_user_sync() {
5121
			$this->_logger->entrance();
5122
5123
			$api = $this->get_api_user_scope();
5124
5125
			// Get user's information.
5126
			$user = $api->get( '/', true );
5127
5128
			if ( isset( $user->id ) ) {
5129
				$this->_user->first = $user->first;
5130
				$this->_user->last  = $user->last;
5131
				$this->_user->email = $user->email;
5132
5133
				if ( ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified ) && $user->is_verified ) {
5134
					$this->_user->is_verified = $user->is_verified;
5135
5136
					$this->do_action( 'account_email_verified', $user->email );
5137
5138
					$this->_admin_notices->add(
5139
						__fs( 'email-verified-message' ),
5140
						__fs( 'right-on' ) . '!'
5141
					);
5142
				}
5143
5144
				// Flush user details to DB.
5145
				$this->_store_user();
5146
5147
				$this->do_action( 'after_account_user_sync', $user );
5148
			}
5149
		}
5150
5151
		/**
5152
		 * @author Vova Feldman (@svovaf)
5153
		 * @since  1.0.5
5154
		 * @uses   FS_Api
5155
		 *
5156
		 * @param bool $flush
5157
		 *
5158
		 * @return object|\FS_Site
5159
		 */
5160
		private function _fetch_site( $flush = false ) {
5161
			$this->_logger->entrance();
5162
			$api = $this->get_api_site_scope();
5163
5164
			$site = $api->get( '/', $flush );
5165
5166
			if ( ! isset( $site->error ) ) {
5167
				$site          = new FS_Site( $site );
5168
				$site->slug    = $this->_slug;
5169
				$site->version = $this->get_plugin_version();
5170
			}
5171
5172
			return $site;
5173
		}
5174
5175
		/**
5176
		 * @param bool $store
5177
		 *
5178
		 * @return FS_Plugin_Plan|object|false
5179
		 */
5180
		private function _enrich_site_plan( $store = true ) {
5181
			// Try to load plan from local cache.
5182
			$plan = $this->_get_plan_by_id( $this->_site->plan->id );
5183
5184
			if ( false === $plan ) {
5185
				$plan = $this->_fetch_site_plan();
5186
			}
5187
5188
			if ( $plan instanceof FS_Plugin_Plan ) {
5189
				$this->_update_plan( $plan, $store );
5190
			}
5191
5192
			return $plan;
5193
		}
5194
5195
		/**
5196
		 * @author Vova Feldman (@svovaf)
5197
		 * @since  1.0.9
5198
		 * @uses   FS_Api
5199
		 *
5200
		 * @param bool $store
5201
		 *
5202
		 * @return FS_Plugin_Plan|object|false
5203
		 */
5204
		private function _enrich_site_trial_plan( $store = true ) {
5205
			// Try to load plan from local cache.
5206
			$trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id );
5207
5208
			if ( false === $trial_plan ) {
5209
				$trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id );
5210
			}
5211
5212
			if ( $trial_plan instanceof FS_Plugin_Plan ) {
5213
				$this->_storage->store( 'trial_plan', $trial_plan, $store );
5214
			}
5215
5216
			return $trial_plan;
5217
		}
5218
5219
		/**
5220
		 * @author Vova Feldman (@svovaf)
5221
		 * @since  1.0.9
5222
		 * @uses   FS_Api
5223
		 *
5224
		 * @param number|bool $license_id
5225
		 *
5226
		 * @return FS_Subscription|object|bool
5227
		 */
5228
		private function _fetch_site_license_subscription( $license_id = false ) {
5229
			$this->_logger->entrance();
5230
			$api = $this->get_api_site_scope();
5231
5232
			if ( ! is_numeric( $license_id ) ) {
5233
				$license_id = $this->_license->id;
5234
			}
5235
5236
			$result = $api->get( "/licenses/{$license_id}/subscriptions.json", true );
5237
5238
			return ! isset( $result->error ) ?
5239
				( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ?
5240
					new FS_Subscription( $result->subscriptions[0] ) :
5241
					false
5242
				) :
5243
				$result;
5244
		}
5245
5246
		/**
5247
		 * @author Vova Feldman (@svovaf)
5248
		 * @since  1.0.4
5249
		 * @uses   FS_Api
5250
		 *
5251
		 * @param number|bool $plan_id
5252
		 *
5253
		 * @return FS_Plugin_Plan|object
5254
		 */
5255
		private function _fetch_site_plan( $plan_id = false ) {
5256
			$this->_logger->entrance();
5257
			$api = $this->get_api_site_scope();
5258
5259
			if ( ! is_numeric( $plan_id ) ) {
5260
				$plan_id = $this->_site->plan->id;
5261
			}
5262
5263
			$plan = $api->get( "/plans/{$plan_id}.json", true );
5264
5265
			return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan;
5266
		}
5267
5268
		/**
5269
		 * @author Vova Feldman (@svovaf)
5270
		 * @since  1.0.5
5271
		 * @uses   FS_Api
5272
		 *
5273
		 * @return FS_Plugin_Plan[]|object
5274
		 */
5275
		private function _fetch_plugin_plans() {
5276
			$this->_logger->entrance();
5277
			$api = $this->get_api_site_scope();
5278
5279
			$result = $api->get( '/plans.json', true );
5280
5281
			if ( ! $this->is_api_error( $result ) ) {
5282
				for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) {
5283
					$result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] );
5284
				}
5285
5286
				$result = $result->plans;
5287
			}
5288
5289
			return $result;
5290
		}
5291
5292
		/**
5293
		 * @author Vova Feldman (@svovaf)
5294
		 * @since  1.0.5
5295
		 * @uses   FS_Api
5296
		 *
5297
		 * @param number|bool $plugin_id
5298
		 *
5299
		 * @return FS_Plugin_License[]|object
5300
		 */
5301
		private function _fetch_licenses( $plugin_id = false ) {
5302
			$this->_logger->entrance();
5303
5304
			$api = $this->get_api_user_scope();
5305
5306
			if ( ! is_numeric( $plugin_id ) ) {
5307
				$plugin_id = $this->_plugin->id;
5308
			}
5309
5310
			$result = $api->get( "/plugins/{$plugin_id}/licenses.json", true );
5311
5312
			if ( ! isset( $result->error ) ) {
5313
				for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) {
5314
					$result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] );
5315
				}
5316
5317
				$result = $result->licenses;
5318
			}
5319
5320
			return $result;
5321
		}
5322
5323
		/**
5324
		 * @author Vova Feldman (@svovaf)
5325
		 * @since  1.0.4
5326
		 *
5327
		 * @param FS_Plugin_Plan $plan
5328
		 * @param bool           $store
5329
		 */
5330
		private function _update_plan( $plan, $store = false ) {
5331
			$this->_logger->entrance();
5332
5333
			$this->_site->plan = $plan;
5334
			$this->_store_site( $store );
5335
		}
5336
5337
		/**
5338
		 * @author Vova Feldman (@svovaf)
5339
		 * @since  1.0.5
5340
		 *
5341
		 * @param FS_Plugin_License[] $licenses
5342
		 * @param string|bool         $plugin_slug
5343
		 */
5344
		private function _update_licenses( $licenses, $plugin_slug = false ) {
5345
			$this->_logger->entrance();
5346
5347
			if ( is_array( $licenses ) ) {
5348
				for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) {
5349
					$licenses[ $i ]->updated = time();
5350
				}
5351
			}
5352
5353
			if ( ! is_string( $plugin_slug ) ) {
5354
				$this->_licenses = $licenses;
5355
			}
5356
5357
			$this->_store_licenses( true, $plugin_slug, $licenses );
5358
		}
5359
5360
		/**
5361
		 * @author Vova Feldman (@svovaf)
5362
		 * @since  1.0.4
5363
		 *
5364
		 * @param bool|number $plugin_id
5365
		 *
5366
		 * @return object|false New plugin tag info if exist.
5367
		 */
5368
		private function _fetch_newer_version( $plugin_id = false ) {
5369
			$latest_tag = $this->_fetch_latest_version( $plugin_id );
5370
5371
			if ( ! is_object( $latest_tag ) ) {
5372
				return false;
5373
			}
5374
5375
			// Check if version is actually newer.
5376
			$has_new_version =
5377
				// If it's an non-installed add-on then always return latest.
5378
				( $this->_is_addon_id( $plugin_id ) && ! $this->is_addon_activated( $plugin_id ) ) ||
5379
				// Compare versions.
5380
				version_compare( $this->get_plugin_version(), $latest_tag->version, '<' );
5381
5382
			$this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' );
5383
5384
			return $has_new_version ? $latest_tag : false;
5385
		}
5386
5387
		/**
5388
		 * @author Vova Feldman (@svovaf)
5389
		 * @since  1.0.5
5390
		 *
5391
		 * @param bool|number $plugin_id
5392
		 *
5393
		 * @return bool|FS_Plugin_Tag
5394
		 */
5395
		function get_update( $plugin_id = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5396
			$this->_logger->entrance();
5397
5398
			if ( ! is_numeric( $plugin_id ) ) {
5399
				$plugin_id = $this->_plugin->id;
5400
			}
5401
5402
			$this->_check_updates( true, $plugin_id );
5403
			$updates = $this->get_all_updates();
5404
5405
			return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
5406
		}
5407
5408
		/**
5409
		 * Check if site assigned with active license.
5410
		 *
5411
		 * @author Vova Feldman (@svovaf)
5412
		 * @since  1.0.6
5413
		 */
5414
		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...
5415
			return (
5416
				is_object( $this->_license ) &&
5417
				is_numeric( $this->_license->id ) &&
5418
				! $this->_license->is_expired()
5419
			);
5420
		}
5421
5422
		/**
5423
		 * Check if site assigned with license with enabled features.
5424
		 *
5425
		 * @author Vova Feldman (@svovaf)
5426
		 * @since  1.0.6
5427
		 *
5428
		 * @return bool
5429
		 */
5430
		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...
5431
			return (
5432
				is_object( $this->_license ) &&
5433
				is_numeric( $this->_license->id ) &&
5434
				$this->_license->is_features_enabled()
5435
			);
5436
		}
5437
5438
		/**
5439
		 * Sync site's plan.
5440
		 *
5441
		 * @author Vova Feldman (@svovaf)
5442
		 * @since  1.0.3
5443
		 *
5444
		 * @uses   FS_Api
5445
		 *
5446
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
5447
		 *                         the admin.
5448
		 */
5449
		private function _sync_license( $background = false ) {
5450
			$this->_logger->entrance();
5451
5452
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
5453
5454
			$is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() );
5455
5456
			if ( $is_addon_sync ) {
5457
				$this->_sync_addon_license( $plugin_id, $background );
5458
			} else {
5459
				$this->_sync_plugin_license( $background );
5460
			}
5461
5462
			$this->do_action( 'after_account_plan_sync', $this->_site->plan->name );
5463
		}
5464
5465
		/**
5466
		 * Sync plugin's add-on license.
5467
		 *
5468
		 * @author Vova Feldman (@svovaf)
5469
		 * @since  1.0.6
5470
		 * @uses   FS_Api
5471
		 *
5472
		 * @param number $addon_id
5473
		 * @param bool   $background
5474
		 */
5475
		private function _sync_addon_license( $addon_id, $background ) {
5476
			$this->_logger->entrance();
5477
5478
			if ( $this->is_addon_activated( $addon_id ) ) {
5479
				// If already installed, use add-on sync.
5480
				$fs_addon = self::get_instance_by_id( $addon_id );
5481
				$fs_addon->_sync_license( $background );
5482
5483
				return;
5484
			}
5485
5486
			// Validate add-on exists.
5487
			$addon = $this->get_addon( $addon_id );
5488
5489
			if ( ! is_object( $addon ) ) {
5490
				return;
5491
			}
5492
5493
			// Add add-on into account add-ons.
5494
			$account_addons = $this->get_account_addons();
5495
			if ( ! is_array( $account_addons ) ) {
5496
				$account_addons = array();
5497
			}
5498
			$account_addons[] = $addon->id;
5499
			$account_addons   = array_unique( $account_addons );
5500
			$this->_store_account_addons( $account_addons );
5501
5502
			// Load add-on licenses.
5503
			$licenses = $this->_fetch_licenses( $addon->id );
5504
5505
			// Sync add-on licenses.
5506
			if ( ! isset( $licenses->error ) ) {
5507
				$this->_update_licenses( $licenses, $addon->slug );
5508
5509
				if ( ! $this->is_addon_installed( $addon->slug ) && FS_License_Manager::has_premium_license( $licenses ) ) {
5510
					$plans_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$addon_id}/plans.json" );
5511
5512
					if ( ! isset( $plans_result->error ) ) {
5513
						$plans = array();
5514
						foreach ( $plans_result->plans as $plan ) {
5515
							$plans[] = new FS_Plugin_Plan( $plan );
5516
						}
5517
5518
						$this->_admin_notices->add_sticky(
5519
							FS_Plan_Manager::instance()->has_free_plan( $plans ) ?
5520
								sprintf(
5521
									__fs( 'addon-successfully-upgraded-message' ),
5522
									$addon->title
5523
								) . ' ' . $this->_get_latest_download_link(
5524
									__fs( 'download-latest-version' ),
5525
									$addon_id
5526
								)
5527
								:
5528
								sprintf(
5529
									__fs( 'addon-successfully-purchased-message' ),
5530
									$addon->title
5531
								) . ' ' . $this->_get_latest_download_link(
5532
									__fs( 'download-latest-version' ),
5533
									$addon_id
5534
								),
5535
							'addon_plan_upgraded_' . $addon->slug,
5536
							__fs( 'yee-haw' ) . '!'
5537
						);
5538
					}
5539
				}
5540
			}
5541
		}
5542
5543
		/**
5544
		 * Sync site's plugin plan.
5545
		 *
5546
		 * @author Vova Feldman (@svovaf)
5547
		 * @since  1.0.6
5548
		 * @uses   FS_Api
5549
		 *
5550
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
5551
		 *                         the admin.
5552
		 */
5553
		private function _sync_plugin_license( $background = false ) {
5554
			$this->_logger->entrance();
5555
5556
			// Sync site info.
5557
			$site = $this->send_install_update( array(), true );
5558
5559
			$plan_change = 'none';
5560
5561
			if ( $this->is_api_error( $site ) ) {
5562
				$api = $this->get_api_site_scope();
5563
5564
				// Try to ping API to see if not blocked.
5565
				if ( ! $api->test() ) {
5566
					// Failed to ping API - blocked!
5567
					$this->_admin_notices->add(
5568
						sprintf(
5569
							__fs( 'server-blocking-access' ),
5570
							$this->get_plugin_name(),
5571
							'<a href="' . $api->get_url() . '" target="_blank">' . $api->get_url() . '</a>'
5572
						) . '<br> ' . __fs( 'server-error-message' ) . var_export( $site->error, true ),
5573
						__fs( 'oops' ) . '...',
5574
						'error',
5575
						$background
5576
					);
5577
				} else {
5578
					// Authentication params are broken.
5579
					$this->_admin_notices->add(
5580
						__fs( 'wrong-authentication-param-message' ),
5581
						__fs( 'oops' ) . '...',
5582
						'error'
5583
					);
5584
				}
5585
			} else {
5586
				$site = new FS_Site( $site );
5587
5588
				// Sync licenses.
5589
				$this->_sync_licenses();
5590
5591
				// Sync plans.
5592
				$this->_sync_plans();
5593
5594
				// Check if plan / license changed.
5595
				if ( ! FS_Entity::equals( $site->plan, $this->_site->plan ) ||
5596
				     // Check if trial started.
5597
				     $site->trial_plan_id != $this->_site->trial_plan_id ||
5598
				     $site->trial_ends != $this->_site->trial_ends ||
5599
				     // Check if license changed.
5600
				     $site->license_id != $this->_site->license_id
5601
				) {
5602
					if ( $site->is_trial() && ! $this->_site->is_trial() ) {
5603
						// New trial started.
5604
						$this->_site = $site;
5605
						$plan_change = 'trial_started';
5606
5607
						// Store trial plan information.
5608
						$this->_enrich_site_trial_plan( true );
5609
5610
					} else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) {
5611
						// Was in trial, but now trial expired and no license ID.
5612
						// New trial started.
5613
						$this->_site = $site;
5614
						$plan_change = 'trial_expired';
5615
5616
						// Clear trial plan information.
5617
						$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...
5618
5619
					} else {
5620
						$is_free = $this->is_free_plan();
5621
5622
						// Make sure license exist and not expired.
5623
						$new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id );
5624
5625
						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...
5626
							// The license is expired, so ignore upgrade method.
5627
						} else {
5628
							// License changed.
5629
							$this->_site = $site;
5630
							$this->_update_site_license( $new_license );
5631
							$this->_store_licenses();
5632
							$this->_enrich_site_plan( true );
5633
5634
							$plan_change = $is_free ?
5635
								'upgraded' :
5636
								( is_object( $new_license ) ?
5637
									'changed' :
5638
									'downgraded' );
5639
						}
5640
					}
5641
5642
					// Store updated site info.
5643
					$this->_store_site();
5644
				} else {
5645
					if ( is_object( $this->_license ) && $this->_license->is_expired() ) {
5646
						if ( ! $this->has_features_enabled_license() ) {
5647
							$this->_deactivate_license();
5648
							$plan_change = 'downgraded';
5649
						} else {
5650
							$plan_change = 'expired';
5651
						}
5652
					}
5653
5654
					if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) {
5655
						$this->_sync_site_subscription( $this->_license );
5656
					}
5657
				}
5658
			}
5659
5660
			switch ( $plan_change ) {
5661
				case 'none':
5662
					if ( ! $background && is_admin() ) {
5663
						$this->_admin_notices->add(
5664
							sprintf(
5665
								__fs( 'plan-did-not-change-message' ) . ' ' .
5666
								sprintf(
5667
									'<a href="%s">%s</a>',
5668
									$this->contact_url(
5669
										'bug',
5670
										sprintf( __fs( 'plan-did-not-change-email-message', 'freemius' ),
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

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

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

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

Loading history...
5671
											strtoupper( $this->_site->plan->name )
5672
										)
5673
									),
5674
									__fs( 'contact-us-here' )
5675
								)
5676
							),
5677
							__fs( 'hmm' ) . '...',
5678
							'error'
5679
						);
5680
					}
5681
					break;
5682
				case 'upgraded':
5683
					$this->_admin_notices->add_sticky(
5684
						sprintf(
5685
							__fs( 'plan-upgraded-message' ),
5686
							'<i>' . $this->get_plugin_name() . '</i>'
5687
						) . ( $this->is_premium() ? '' : ' ' . $this->_get_latest_download_link( sprintf(
5688
								__fs( 'download-latest-x-version' ),
5689
								$this->_site->plan->title
5690
							) )
5691
						),
5692
						'plan_upgraded',
5693
						__fs( 'yee-haw' ) . '!'
5694
					);
5695
5696
					$this->_admin_notices->remove_sticky( array(
5697
						'trial_started',
5698
						'trial_promotion',
5699
						'trial_expired',
5700
						'activation_complete',
5701
					) );
5702
					break;
5703
				case 'changed':
5704
					$this->_admin_notices->add_sticky(
5705
						sprintf(
5706
							__fs( 'plan-changed-to-x-message' ),
5707
							$this->_site->plan->title
5708
						),
5709
						'plan_changed'
5710
					);
5711
5712
					$this->_admin_notices->remove_sticky( array(
5713
						'trial_started',
5714
						'trial_promotion',
5715
						'trial_expired',
5716
						'activation_complete',
5717
					) );
5718
					break;
5719
				case 'downgraded':
5720
					$this->_admin_notices->add_sticky(
5721
						sprintf( __fs( 'license-expired-blocking-message' ) ),
5722
						'license_expired',
5723
						__fs( 'hmm' ) . '...'
5724
					);
5725
					$this->_admin_notices->remove_sticky( 'plan_upgraded' );
5726
					break;
5727
				case 'expired':
5728
					$this->_admin_notices->add_sticky(
5729
						sprintf( __fs( 'license-expired-non-blocking-message' ), $this->_site->plan->title ),
5730
						'license_expired',
5731
						__fs( 'hmm' ) . '...'
5732
					);
5733
					$this->_admin_notices->remove_sticky( 'plan_upgraded' );
5734
					break;
5735
				case 'trial_started':
5736
					$this->_admin_notices->add_sticky(
5737
						sprintf(
5738
							__fs( 'trial-started-message' ),
5739
							'<i>' . $this->get_plugin_name() . '</i>'
5740
						) . ( $this->is_premium() ? '' : ' ' . $this->_get_latest_download_link( sprintf(
5741
								__fs( 'download-latest-x-version' ),
5742
								$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...
5743
							) ) ),
5744
						'trial_started',
5745
						__fs( 'yee-haw' ) . '!'
5746
					);
5747
5748
					$this->_admin_notices->remove_sticky( array(
5749
						'trial_promotion',
5750
					) );
5751
					break;
5752
				case 'trial_expired':
5753
					$this->_admin_notices->add_sticky(
5754
						__fs( 'trial-expired-message' ),
5755
						'trial_expired',
5756
						__fs( 'hmm' ) . '...'
5757
					);
5758
					$this->_admin_notices->remove_sticky( array(
5759
						'trial_started',
5760
						'trial_promotion',
5761
						'plan_upgraded',
5762
					) );
5763
					break;
5764
			}
5765
5766
			if ( 'none' !== $plan_change ) {
5767
				$this->do_action( 'after_license_change', $plan_change, $this->_site->plan );
5768
			}
5769
		}
5770
5771
		/**
5772
		 * @author Vova Feldman (@svovaf)
5773
		 * @since  1.0.5
5774
		 *
5775
		 * @param bool $background
5776
		 */
5777
		protected function _activate_license( $background = false ) {
5778
			$this->_logger->entrance();
5779
5780
			$premium_license = $this->_get_available_premium_license();
5781
5782
			if ( ! is_object( $premium_license ) ) {
5783
				return;
5784
			}
5785
5786
			$api     = $this->get_api_site_scope();
5787
			$license = $api->call( "/licenses/{$premium_license->id}.json", 'put' );
5788
5789
			if ( isset( $license->error ) ) {
5790
				if ( ! $background ) {
5791
					$this->_admin_notices->add(
5792
						__fs( 'license-activation-failed-message' ) . '<br> ' .
5793
						__fs( 'server-error-message' ) . ' ' . var_export( $license->error, true ),
5794
						__fs( 'hmm' ) . '...',
5795
						'error'
5796
					);
5797
				}
5798
5799
				return;
5800
			}
5801
5802
			$premium_license = new FS_Plugin_License( $license );
5803
5804
			// Updated site plan.
5805
			$this->_site->plan->id = $premium_license->plan_id;
5806
			$this->_update_site_license( $premium_license );
5807
			$this->_enrich_site_plan( false );
5808
5809
			$this->_store_account();
5810
5811
			if ( ! $background ) {
5812
				$this->_admin_notices->add_sticky(
5813
					__fs( 'license-activated-message' ) .
5814
					( $this->is_premium() ? '' : ' ' . $this->_get_latest_download_link( sprintf(
5815
							__fs( 'download-latest-x-version' ),
5816
							$this->_site->plan->title
5817
						) ) ),
5818
					'license_activated',
5819
					__fs( 'yee-haw' ) . '!'
5820
				);
5821
			}
5822
5823
			$this->_admin_notices->remove_sticky( array(
5824
				'trial_promotion',
5825
				'license_expired',
5826
			) );
5827
		}
5828
5829
		/**
5830
		 * @author Vova Feldman (@svovaf)
5831
		 * @since  1.0.5
5832
		 *
5833
		 * @param bool $show_notice
5834
		 */
5835
		protected function _deactivate_license( $show_notice = true ) {
5836
			$this->_logger->entrance();
5837
5838
			if ( ! is_object( $this->_license ) ) {
5839
				$this->_admin_notices->add(
5840
					sprintf( __fs( 'no-active-license-message' ), $this->_site->plan->title ),
5841
					__fs( 'hmm' ) . '...'
5842
				);
5843
5844
				return;
5845
			}
5846
5847
			$api     = $this->get_api_site_scope();
5848
			$license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' );
5849
5850
			if ( isset( $license->error ) ) {
5851
				$this->_admin_notices->add(
5852
					__fs( 'license-deactivation-failed-message' ) . '<br> ' .
5853
					__fs( 'server-error-message' ) . ' ' . var_export( $license->error, true ),
5854
					__fs( 'hmm' ) . '...',
5855
					'error'
5856
				);
5857
5858
				return;
5859
			}
5860
5861
			// Update license cache.
5862
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
5863
				if ( $license->id == $this->_licenses[ $i ]->id ) {
5864
					$this->_licenses[ $i ] = new FS_Plugin_License( $license );
5865
				}
5866
			}
5867
5868
			// Updated site plan to default.
5869
			$this->_sync_plans();
5870
			$this->_site->plan->id = $this->_plans[0]->id;
5871
			// Unlink license from site.
5872
			$this->_update_site_license( null );
5873
			$this->_enrich_site_plan( false );
5874
5875
			$this->_store_account();
5876
5877
			if ( $show_notice ) {
5878
				$this->_admin_notices->add(
5879
					sprintf( __fs( 'license-deactivation-message' ), $this->_site->plan->title ),
5880
					__fs( 'ok' )
5881
				);
5882
			}
5883
5884
			$this->_admin_notices->remove_sticky( array(
5885
				'plan_upgraded',
5886
				'license_activated',
5887
			) );
5888
		}
5889
5890
		/**
5891
		 * Site plan downgrade.
5892
		 *
5893
		 * @author Vova Feldman (@svovaf)
5894
		 * @since  1.0.4
5895
		 *
5896
		 * @uses   FS_Api
5897
		 */
5898
		private function _downgrade_site() {
5899
			$this->_logger->entrance();
5900
5901
			$api  = $this->get_api_site_scope();
5902
			$site = $api->call( 'downgrade.json', 'put' );
5903
5904
			$plan_downgraded = false;
5905
			$plan            = false;
5906
			if ( ! isset( $site->error ) ) {
5907
				$prev_plan_id = $this->_site->plan->id;
5908
5909
				// Update new site plan id.
5910
				$this->_site->plan->id = $site->plan_id;
5911
5912
				$plan         = $this->_enrich_site_plan();
5913
				$subscription = $this->_sync_site_subscription( $this->_license );
5914
5915
				// Plan downgraded if plan was changed or subscription was cancelled.
5916
				$plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) ||
5917
				                   ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() );
5918
			} 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...
5919
				// handle different error cases.
5920
5921
			}
5922
5923
			if ( $plan_downgraded ) {
5924
				// Remove previous sticky message about upgrade (if exist).
5925
				$this->_admin_notices->remove_sticky( 'plan_upgraded' );
5926
5927
				$this->_admin_notices->add(
5928
					sprintf( __fs( 'plan-x-downgraded-message' ),
5929
						$plan->title,
5930
						human_time_diff( time(), strtotime( $this->_license->expiration ) )
5931
					)
5932
				);
5933
5934
				// Store site updates.
5935
				$this->_store_site();
5936
			} else {
5937
				$this->_admin_notices->add(
5938
					__fs( 'plan-downgraded-failure-message' ),
5939
					__fs( 'oops' ) . '...',
5940
					'error'
5941
				);
5942
			}
5943
		}
5944
5945
		/**
5946
		 * Cancel site trial.
5947
		 *
5948
		 * @author Vova Feldman (@svovaf)
5949
		 * @since  1.0.9
5950
		 *
5951
		 * @uses   FS_Api
5952
		 */
5953
		private function _cancel_trial() {
5954
			$this->_logger->entrance();
5955
5956
			if ( ! $this->is_trial() ) {
5957
				$this->_admin_notices->add(
5958
					__fs( 'trial-cancel-no-trial-message' ),
5959
					__fs( 'oops' ) . '...',
5960
					'error'
5961
				);
5962
5963
				return;
5964
			}
5965
5966
			$api  = $this->get_api_site_scope();
5967
			$site = $api->call( 'trials.json', 'delete' );
5968
5969
			$trial_cancelled = false;
5970
5971
			if ( ! isset( $site->error ) ) {
5972
				$prev_trial_ends = $this->_site->trial_ends;
5973
5974
				// Update new site plan id.
5975
				$this->_site->trial_ends = $site->trial_ends;
5976
5977
				$trial_cancelled = ( $prev_trial_ends != $site->trial_ends );
5978
			} 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...
5979
				// handle different error cases.
5980
5981
			}
5982
5983
			if ( $trial_cancelled ) {
5984
				// Remove previous sticky message about upgrade (if exist).
5985
				$this->_admin_notices->remove_sticky( 'plan_upgraded' );
5986
5987
				$this->_admin_notices->add(
5988
					sprintf( __fs( 'trial-cancel-message' ), $this->_storage->trial_plan->title )
0 ignored issues
show
Documentation introduced by
The property trial_plan does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
5989
				);
5990
5991
				$this->_admin_notices->remove_sticky( array(
5992
					'trial_started',
5993
					'trial_promotion',
5994
					'plan_upgraded',
5995
				) );
5996
5997
				// Store site updates.
5998
				$this->_store_site();
5999
6000
				// Clear trial plan information.
6001
				unset( $this->_storage->trial_plan );
6002
			} else {
6003
				$this->_admin_notices->add(
6004
					__fs( 'trial-cancel-failure-message' ),
6005
					__fs( 'oops' ) . '...',
6006
					'error'
6007
				);
6008
			}
6009
		}
6010
6011
		/**
6012
		 * @author Vova Feldman (@svovaf)
6013
		 * @since  1.0.6
6014
		 *
6015
		 * @param bool|number $plugin_id
6016
		 *
6017
		 * @return bool
6018
		 */
6019
		private function _is_addon_id( $plugin_id ) {
6020
			return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id );
6021
		}
6022
6023
		/**
6024
		 * Check if user eligible to download premium version updates.
6025
		 *
6026
		 * @author Vova Feldman (@svovaf)
6027
		 * @since  1.0.6
6028
		 *
6029
		 * @return bool
6030
		 */
6031
		private function _can_download_premium() {
6032
			return $this->has_active_license() ||
6033
			       ( $this->is_trial() && ! $this->get_trial_plan()->is_free() );
6034
		}
6035
6036
		/**
6037
		 *
6038
		 * @author Vova Feldman (@svovaf)
6039
		 * @since  1.0.6
6040
		 *
6041
		 * @param bool|number $addon_id
6042
		 * @param string      $type "json" or "zip"
6043
		 *
6044
		 * @return string
6045
		 */
6046
		private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) {
6047
6048
			$is_addon = $this->_is_addon_id( $addon_id );
6049
6050
			$is_premium = null;
6051
			if ( ! $is_addon ) {
6052
				$is_premium = $this->_can_download_premium();
6053
			} else if ( $this->is_addon_activated( $addon_id ) ) {
6054
				$is_premium = self::get_instance_by_id( $addon_id )->_can_download_premium();
6055
			}
6056
6057
			return // If add-on, then append add-on ID.
6058
				( $is_addon ? "/addons/$addon_id" : '' ) .
6059
				'/updates/latest.' . $type .
6060
				// If add-on and not yet activated, try to fetch based on server licensing.
6061
				( is_bool( $is_premium ) ? '?is_premium=' . json_encode( $is_premium ) : '' );
6062
		}
6063
6064
		/**
6065
		 * @author Vova Feldman (@svovaf)
6066
		 * @since  1.0.4
6067
		 *
6068
		 * @param bool|number $addon_id
6069
		 *
6070
		 * @return object|false Plugin latest tag info.
6071
		 */
6072
		function _fetch_latest_version( $addon_id = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6073
			$tag            = $this->get_api_site_or_plugin_scope()->get(
6074
				$this->_get_latest_version_endpoint( $addon_id, 'json' ),
6075
				true
6076
			);
6077
			$latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get';
6078
			$this->_logger->departure( 'Latest version ' . $latest_version );
6079
6080
			return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false;
6081
		}
6082
6083
		#region Download Plugin ------------------------------------------------------------------
6084
6085
		/**
6086
		 * Download latest plugin version, based on plan.
6087
		 * The download will be fetched via the API first.
6088
		 *
6089
		 * @author Vova Feldman (@svovaf)
6090
		 * @since  1.0.4
6091
		 *
6092
		 * @param bool|number $plugin_id
6093
		 *
6094
		 * @uses   FS_Api
6095
		 *
6096
		 * @deprecated
6097
		 */
6098
		private function _download_latest( $plugin_id = false ) {
6099
			$this->_logger->entrance();
6100
6101
			$is_addon = $this->_is_addon_id( $plugin_id );
6102
6103
			$is_premium = $this->_can_download_premium();
6104
6105
			$latest = $this->get_api_site_scope()->call(
6106
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
6107
			);
6108
6109
			$slug = $this->_slug;
6110
			if ( $is_addon ) {
6111
				$addon = $this->get_addon( $plugin_id );
6112
				$slug  = is_object( $addon ) ? $addon->slug : 'addon';
6113
			}
6114
6115
			if ( ! is_object( $latest ) ) {
6116
				header( "Content-Type: application/zip" );
6117
				header( "Content-Disposition: attachment; filename={$slug}" . ( ! $is_addon && $is_premium ? '-premium' : '' ) . ".zip" );
6118
				header( "Content-Length: " . strlen( $latest ) );
6119
				echo $latest;
6120
6121
				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...
6122
			}
6123
		}
6124
6125
		/**
6126
		 * Download latest plugin version, based on plan.
6127
		 *
6128
		 * Not like _download_latest(), this will redirect the page
6129
		 * to secure download url to prevent dual download (from FS to WP server,
6130
		 * and then from WP server to the client / browser).
6131
		 *
6132
		 * @author Vova Feldman (@svovaf)
6133
		 * @since  1.0.9
6134
		 *
6135
		 * @param bool|number $plugin_id
6136
		 *
6137
		 * @uses   FS_Api
6138
		 * @uses   wp_redirect()
6139
		 */
6140
		private function _download_latest_directly( $plugin_id = false ) {
6141
			$this->_logger->entrance();
6142
6143
			wp_redirect( $this->_get_latest_download_api_url( $plugin_id ) );
6144
		}
6145
6146
		/**
6147
		 * Get latest plugin FS API download URL.
6148
		 *
6149
		 * @author Vova Feldman (@svovaf)
6150
		 * @since  1.0.9
6151
		 *
6152
		 * @param bool|number $plugin_id
6153
		 *
6154
		 * @return string
6155
		 */
6156
		private function _get_latest_download_api_url( $plugin_id = false ) {
6157
			$this->_logger->entrance();
6158
6159
			return $this->get_api_site_scope()->get_signed_url(
6160
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
6161
			);
6162
		}
6163
6164
		/**
6165
		 * Get latest plugin download link.
6166
		 *
6167
		 * @author Vova Feldman (@svovaf)
6168
		 * @since  1.0.9
6169
		 *
6170
		 * @param string      $label
6171
		 * @param bool|number $plugin_id
6172
		 *
6173
		 * @return string
6174
		 */
6175
		private function _get_latest_download_link( $label, $plugin_id = false ) {
6176
			return sprintf(
6177
				'<a target="_blank" href="%s">%s</a>',
6178
				$this->_get_latest_download_local_url( $plugin_id ),
6179
				$label
6180
			);
6181
		}
6182
6183
		/**
6184
		 * Get latest plugin download local URL.
6185
		 *
6186
		 * @author Vova Feldman (@svovaf)
6187
		 * @since  1.0.9
6188
		 *
6189
		 * @param bool|number $plugin_id
6190
		 *
6191
		 * @return string
6192
		 */
6193
		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...
6194
			// Add timestamp to protect from caching.
6195
			$params = array( 'ts' => WP_FS__SCRIPT_START_TIME );
6196
6197
			if ( ! empty( $plugin_id ) ) {
6198
				$params['plugin_id'] = $plugin_id;
6199
			}
6200
6201
			return $this->get_account_url( 'download_latest', $params );
6202
		}
6203
6204
		#endregion Download Plugin ------------------------------------------------------------------
6205
6206
		/**
6207
		 * @author Vova Feldman (@svovaf)
6208
		 * @since  1.0.4
6209
		 *
6210
		 * @uses   FS_Api
6211
		 *
6212
		 * @param bool        $background Hints the method if it's a background updates check. If false, it means that
6213
		 *                                was initiated by the admin.
6214
		 * @param bool|number $plugin_id
6215
		 */
6216
		private function _check_updates( $background = false, $plugin_id = false ) {
6217
			$this->_logger->entrance();
6218
6219
			// Check if there's a newer version for download.
6220
			$new_version = $this->_fetch_newer_version( $plugin_id );
6221
6222
			$update = null;
6223
			if ( is_object( $new_version ) ) {
6224
				$update = new FS_Plugin_Tag( $new_version );
6225
6226
				if ( ! $background ) {
6227
					$this->_admin_notices->add(
6228
						sprintf(
6229
							__fs( 'version-x-released' ) . ' ' . __fS( 'please-download-x' ),
6230
							$update->version,
6231
							sprintf(
6232
								'<a href="%s" target="_blank">%s</a>',
6233
								$this->get_account_url( 'download_latest' ),
6234
								sprintf( __fs( 'latest-x-version' ), $this->_site->plan->title )
6235
							)
6236
						),
6237
						__fs( 'new' ) . '!'
6238
					);
6239
				}
6240
			} else if ( false === $new_version && ! $background ) {
6241
				$this->_admin_notices->add(
6242
					__fs( 'you-have-latest' ),
6243
					__fs( 'you-are-good' )
6244
				);
6245
			}
6246
6247
			$this->_store_update( $update, true, $plugin_id );
6248
		}
6249
6250
		/**
6251
		 * @author Vova Feldman (@svovaf)
6252
		 * @since  1.0.4
6253
		 *
6254
		 * @uses   FS_Api
6255
		 *
6256
		 */
6257
		private function _sync_addons() {
6258
			$this->_logger->entrance();
6259
6260
			$result = $this->get_api_site_or_plugin_scope()->get( '/addons.json?enriched=true', true );
6261
6262
			if ( isset( $result->error ) ) {
6263
				return;
6264
			}
6265
6266
			$addons = array();
6267
			for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) {
6268
				$addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] );
6269
			}
6270
6271
			$this->_store_addons( $addons, true );
6272
		}
6273
6274
		/**
6275
		 * Handle user email update.
6276
		 *
6277
		 * @author Vova Feldman (@svovaf)
6278
		 * @since  1.0.3
6279
		 * @uses   FS_Api
6280
		 *
6281
		 * @param string $new_email
6282
		 *
6283
		 * @return object
6284
		 */
6285
		private function _update_email( $new_email ) {
6286
			$this->_logger->entrance();
6287
6288
6289
			$api  = $this->get_api_user_scope();
6290
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array(
6291
				'email'                   => $new_email,
6292
				'after_email_confirm_url' => $this->_get_admin_page_url(
6293
					'account',
6294
					array( 'fs_action' => 'sync_user' )
6295
				),
6296
			) );
6297
6298
			if ( ! isset( $user->error ) ) {
6299
				$this->_user->email       = $user->email;
6300
				$this->_user->is_verified = $user->is_verified;
6301
				$this->_store_user();
6302
			} 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...
6303
				// handle different error cases.
6304
6305
			}
6306
6307
			return $user;
6308
		}
6309
6310
		/**
6311
		 * @author Vova Feldman (@svovaf)
6312
		 * @since  1.1.1
6313
		 *
6314
		 * @param mixed $result
6315
		 *
6316
		 * @return bool Is API result contains an error.
6317
		 */
6318
		private function is_api_error( $result ) {
6319
			return ( is_object( $result ) && isset( $result->error ) ) ||
6320
			       is_string( $result );
6321
		}
6322
6323
		/**
6324
		 * Start install ownership change.
6325
		 *
6326
		 * @author Vova Feldman (@svovaf)
6327
		 * @since  1.1.1
6328
		 * @uses   FS_Api
6329
		 *
6330
		 * @param string $candidate_email
6331
		 *
6332
		 * @return bool Is ownership change successfully initiated.
6333
		 */
6334
		private function init_change_owner( $candidate_email ) {
6335
			$this->_logger->entrance();
6336
6337
			$api    = $this->get_api_site_scope();
6338
			$result = $api->call( "/users/{$this->_user->id}.json", 'put', array(
6339
				'email'             => $candidate_email,
6340
				'after_confirm_url' => $this->_get_admin_page_url(
6341
					'account',
6342
					array( 'fs_action' => 'change_owner' )
6343
				),
6344
			) );
6345
6346
			return ! $this->is_api_error( $result );
6347
		}
6348
6349
		/**
6350
		 * Handle install ownership change.
6351
		 *
6352
		 * @author Vova Feldman (@svovaf)
6353
		 * @since  1.1.1
6354
		 * @uses   FS_Api
6355
		 *
6356
		 * @return bool Was ownership change successfully complete.
6357
		 */
6358
		private function complete_change_owner() {
6359
			$this->_logger->entrance();
6360
6361
			$site_result = $this->get_api_site_scope( true )->get();
6362
			$site        = new FS_Site( $site_result );
6363
			$this->_site = $site;
6364
6365
			$user     = new FS_User();
6366
			$user->id = fs_request_get( 'user_id' );
6367
6368
			// Validate install's user and given user.
6369
			if ( $user->id != $this->_site->user_id ) {
6370
				return false;
6371
			}
6372
6373
			$user->public_key = fs_request_get( 'user_public_key' );
6374
			$user->secret_key = fs_request_get( 'user_secret_key' );
6375
6376
			// Fetch new user information.
6377
			$this->_user = $user;
6378
			$user_result = $this->get_api_user_scope( true )->get();
6379
			$user        = new FS_User( $user_result );
6380
			$this->_user = $user;
6381
6382
			$this->_set_account( $user, $site );
6383
6384
			return true;
6385
		}
6386
6387
		/**
6388
		 * Handle user name update.
6389
		 *
6390
		 * @author Vova Feldman (@svovaf)
6391
		 * @since  1.0.9
6392
		 * @uses   FS_Api
6393
		 *
6394
		 * @return object
6395
		 */
6396
		private function update_user_name() {
6397
			$this->_logger->entrance();
6398
			$name = fs_request_get( 'fs_user_name_' . $this->_slug, '' );
6399
6400
			$api  = $this->get_api_user_scope();
6401
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array(
6402
				'name' => $name,
6403
			) );
6404
6405
			if ( ! isset( $user->error ) ) {
6406
				$this->_user->first = $user->first;
6407
				$this->_user->last  = $user->last;
6408
				$this->_store_user();
6409
			} 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...
6410
				// handle different error cases.
6411
6412
			}
6413
6414
			return $user;
6415
		}
6416
6417
		/**
6418
		 * Verify user email.
6419
		 *
6420
		 * @author Vova Feldman (@svovaf)
6421
		 * @since  1.0.3
6422
		 * @uses   FS_Api
6423
		 */
6424
		private function verify_email() {
6425
			$this->_handle_account_user_sync();
6426
6427
			if ( $this->_user->is_verified() ) {
6428
				return;
6429
			}
6430
6431
			$api    = $this->get_api_site_scope();
6432
			$result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array(
6433
				'after_email_confirm_url' => $this->_get_admin_page_url(
6434
					'account',
6435
					array( 'fs_action' => 'sync_user' )
6436
				)
6437
			) );
6438
6439
			if ( ! isset( $result->error ) ) {
6440
				$this->_admin_notices->add( sprintf(
6441
					__fs( 'verification-email-sent-message' ),
6442
					sprintf( '<a href="mailto:%1s">%2s</a>', esc_url( $this->_user->email ), $this->_user->email )
6443
				) );
6444
			} 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...
6445
				// handle different error cases.
6446
6447
			}
6448
		}
6449
6450
		/**
6451
		 * @author Vova Feldman (@svovaf)
6452
		 * @since  1.1.2
6453
		 *
6454
		 * @return string
6455
		 */
6456
		private function get_activation_url() {
6457
			return $this->apply_filters( 'connect_url', $this->_get_admin_page_url() );
6458
		}
6459
6460
		/**
6461
		 * @author Vova Feldman (@svovaf)
6462
		 * @since  1.1.3
6463
		 *
6464
		 * @param string $filter Filter name.
6465
		 *
6466
		 * @return string
6467
		 */
6468
		private function get_after_activation_url( $filter ) {
6469
			$first_time_path = $this->_menu->get_first_time_path();
6470
6471
			return $this->apply_filters(
6472
				$filter,
6473
				empty( $first_time_path ) ?
6474
					$this->_get_admin_page_url() :
6475
					$first_time_path
6476
			);
6477
		}
6478
6479
		/**
6480
		 * Handle account page updates / edits / actions.
6481
		 *
6482
		 * @author Vova Feldman (@svovaf)
6483
		 * @since  1.0.2
6484
		 *
6485
		 */
6486
		private function _handle_account_edits() {
6487
			if ( ! current_user_can( 'activate_plugins' ) ) {
6488
				return;
6489
			}
6490
6491
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
6492
			$action    = fs_get_action();
6493
6494
			switch ( $action ) {
6495
				case 'delete_account':
6496
					check_admin_referer( $action );
6497
6498
					if ( $plugin_id == $this->get_id() ) {
6499
						$this->delete_account_event();
6500
6501
						// Clear user and site.
6502
						$this->_site = null;
6503
						$this->_user = null;
6504
6505
						if ( fs_redirect( $this->get_activation_url() ) ) {
6506
							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...
6507
						}
6508
					} else {
6509
						if ( $this->is_addon_activated( $plugin_id ) ) {
6510
							$fs_addon = self::get_instance_by_id( $plugin_id );
6511
							$fs_addon->delete_account_event();
6512
6513
							if ( fs_redirect( $this->_get_admin_page_url( 'account' ) ) ) {
6514
								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...
6515
							}
6516
						}
6517
					}
6518
6519
					return;
6520
6521
				case 'downgrade_account':
6522
					check_admin_referer( $action );
6523
					$this->_downgrade_site();
6524
6525
					return;
6526
6527
				case 'activate_license':
6528
					check_admin_referer( $action );
6529
6530
					if ( $plugin_id == $this->get_id() ) {
6531
						$this->_activate_license();
6532
					} else {
6533
						if ( $this->is_addon_activated( $plugin_id ) ) {
6534
							$fs_addon = self::get_instance_by_id( $plugin_id );
6535
							$fs_addon->_activate_license();
6536
						}
6537
					}
6538
6539
					return;
6540
6541
				case 'deactivate_license':
6542
					check_admin_referer( $action );
6543
6544
					if ( $plugin_id == $this->get_id() ) {
6545
						$this->_deactivate_license();
6546
					} else {
6547
						if ( $this->is_addon_activated( $plugin_id ) ) {
6548
							$fs_addon = self::get_instance_by_id( $plugin_id );
6549
							$fs_addon->_deactivate_license();
6550
						}
6551
					}
6552
6553
					return;
6554
6555
				case 'check_updates':
6556
					check_admin_referer( $action );
6557
					$this->_check_updates();
6558
6559
					return;
6560
6561
				case 'change_owner':
6562
					$state = fs_request_get( 'state', 'init' );
6563
					switch ( $state ) {
6564
						case 'init':
6565
							$candidate_email = fs_request_get( 'candidate_email', '' );
6566
6567
							if ( $this->init_change_owner( $candidate_email ) ) {
6568
								$this->_admin_notices->add( sprintf( __fs( 'change-owner-request-sent-x' ), '<b>' . $this->_user->email . '</b>' ) );
6569
							}
6570
							break;
6571
						case 'owner_confirmed':
6572
							$candidate_email = fs_request_get( 'candidate_email', '' );
6573
6574
							$this->_admin_notices->add( sprintf( __fs( 'change-owner-request_owner-confirmed' ), '<b>' . $candidate_email . '</b>' ) );
6575
							break;
6576
						case 'candidate_confirmed':
6577
							if ( $this->complete_change_owner() ) {
6578
								$this->_admin_notices->add_sticky(
6579
									sprintf( __fs( 'change-owner-request_candidate-confirmed' ), '<b>' . $this->_user->email . '</b>' ),
6580
									'ownership_changed',
6581
									__fs( 'congrats' ) . '!'
6582
								);
6583
							} 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...
6584
								// @todo Handle failed ownership change message.
6585
							}
6586
							break;
6587
					}
6588
6589
					return;
6590
6591
				case 'update_email':
6592
					check_admin_referer( 'update_email' );
6593
6594
					$new_email = fs_request_get( 'fs_email_' . $this->_slug, '' );
6595
					$result    = $this->_update_email( $new_email );
6596
6597
					if ( isset( $result->error ) ) {
6598
						switch ( $result->error->code ) {
6599
							case 'user_exist':
6600
								$this->_admin_notices->add(
6601
									__fs( 'user-exist-message' ) . ' ' .
6602
									sprintf( __fs( 'user-exist-message_ownership' ), '<b>' . $new_email . '</b>' ) .
6603
									sprintf(
6604
										'<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
6605
										$this->get_account_url( 'change_owner', array(
6606
											'state'           => 'init',
6607
											'candidate_email' => $new_email
6608
										) ),
6609
										__fs( 'change-ownership' )
6610
									),
6611
									__fs( 'oops' ) . '...',
6612
									'error'
6613
								);
6614
								break;
6615
						}
6616
					} else {
6617
						$this->_admin_notices->add( __fs( 'email-updated-message' ) );
6618
					}
6619
6620
					return;
6621
6622
				case 'update_user_name':
6623
					check_admin_referer( 'update_user_name' );
6624
6625
					$result = $this->update_user_name();
6626
6627
					if ( isset( $result->error ) ) {
6628
						$this->_admin_notices->add(
6629
							__fs( 'name-update-failed-message' ),
6630
							__fs( 'oops' ) . '...',
6631
							'error'
6632
						);
6633
					} else {
6634
						$this->_admin_notices->add( __fs( 'name-updated-message' ) );
6635
					}
6636
6637
					return;
6638
6639
				#region Actions that might be called from external links (e.g. email)
6640
6641
				case 'cancel_trial':
6642
					$this->_cancel_trial();
6643
6644
					return;
6645
6646
				case 'verify_email':
6647
					$this->verify_email();
6648
6649
					return;
6650
6651
				case 'sync_user':
6652
					$this->_handle_account_user_sync();
6653
6654
					return;
6655
6656
				case $this->_slug . '_sync_license':
6657
					$this->_sync_license();
6658
6659
					return;
6660
6661
				case 'download_latest':
6662
					$this->_download_latest_directly( $plugin_id );
6663
6664
					return;
6665
6666
				#endregion
6667
			}
6668
6669
			if ( WP_FS__IS_POST_REQUEST ) {
6670
				$properties = array( 'site_secret_key', 'site_id', 'site_public_key' );
6671
				foreach ( $properties as $p ) {
6672
					if ( 'update_' . $p === $action ) {
6673
						check_admin_referer( $action );
6674
6675
						$this->_logger->log( $action );
6676
6677
						$site_property                      = substr( $p, strlen( 'site_' ) );
6678
						$site_property_value                = fs_request_get( 'fs_' . $p . '_' . $this->_slug, '' );
6679
						$this->get_site()->{$site_property} = $site_property_value;
6680
6681
						// Store account after modification.
6682
						$this->_store_site();
6683
6684
						$this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value );
6685
6686
						$this->_admin_notices->add( sprintf(
6687
							__fs( 'x-updated' ),
6688
							'<b>' . str_replace( '_', ' ', $p ) . '</b>' ) );
6689
6690
						return;
6691
					}
6692
				}
6693
			}
6694
		}
6695
6696
		/**
6697
		 * Account page resources load.
6698
		 *
6699
		 * @author Vova Feldman (@svovaf)
6700
		 * @since  1.0.6
6701
		 */
6702
		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...
6703
			$this->_logger->entrance();
6704
6705
			$this->_logger->info( var_export( $_REQUEST, true ) );
6706
6707
			fs_enqueue_local_style( 'fs_account', '/admin/account.css' );
6708
6709
			if ( $this->_has_addons() ) {
6710
				wp_enqueue_script( 'plugin-install' );
6711
				add_thickbox();
6712
6713
				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...
6714
					$classes .= ' plugins-php';
6715
6716
					return $classes;
6717
				}
6718
6719
				add_filter( 'admin_body_class', 'fs_addons_body_class' );
6720
			}
6721
6722
			$this->_handle_account_edits();
6723
6724
			$this->do_action( 'account_page_load_before_departure' );
6725
		}
6726
6727
		/**
6728
		 * Render account page.
6729
		 *
6730
		 * @author Vova Feldman (@svovaf)
6731
		 * @since  1.0.0
6732
		 */
6733
		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...
6734
			$this->_logger->entrance();
6735
6736
			$vars = array( 'slug' => $this->_slug );
6737
			fs_require_once_template( 'account.php', $vars );
6738
		}
6739
6740
		/**
6741
		 * Render account connect page.
6742
		 *
6743
		 * @author Vova Feldman (@svovaf)
6744
		 * @since  1.0.7
6745
		 */
6746
		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...
6747
			$this->_logger->entrance();
6748
6749
			$vars = array( 'slug' => $this->_slug );
6750
			if ( $this->is_pending_activation() ) {
6751
				fs_require_once_template( 'pending-activation.php', $vars );
6752
			} else {
6753
				fs_require_once_template( 'connect.php', $vars );
6754
			}
6755
		}
6756
6757
		/**
6758
		 * Load required resources before add-ons page render.
6759
		 *
6760
		 * @author Vova Feldman (@svovaf)
6761
		 * @since  1.0.6
6762
		 */
6763
		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...
6764
			$this->_logger->entrance();
6765
6766
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
6767
6768
			wp_enqueue_script( 'plugin-install' );
6769
			add_thickbox();
6770
6771
			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...
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 (L6713-6717) 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...
6772
				$classes .= ' plugins-php';
6773
6774
				return $classes;
6775
			}
6776
6777
			add_filter( 'admin_body_class', 'fs_addons_body_class' );
6778
6779
			if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) {
6780
				$this->_admin_notices->add(
6781
					sprintf( __fs( 'addons-info-external-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
6782
					__fs( 'heads-up' ),
6783
					'update-nag'
6784
				);
6785
			}
6786
		}
6787
6788
		/**
6789
		 * Render add-ons page.
6790
		 *
6791
		 * @author Vova Feldman (@svovaf)
6792
		 * @since  1.0.6
6793
		 */
6794
		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...
6795
			$this->_logger->entrance();
6796
6797
			$vars = array( 'slug' => $this->_slug );
6798
			fs_require_once_template( 'add-ons.php', $vars );
6799
		}
6800
6801
		/* Pricing & Upgrade
6802
		------------------------------------------------------------------------------------------------------------------*/
6803
		/**
6804
		 * Render pricing page.
6805
		 *
6806
		 * @author Vova Feldman (@svovaf)
6807
		 * @since  1.0.0
6808
		 */
6809
		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...
6810
			$this->_logger->entrance();
6811
6812
			$vars = array( 'slug' => $this->_slug );
6813
6814
			if ( 'true' === fs_request_get( 'checkout', false ) ) {
6815
				fs_require_once_template( 'checkout.php', $vars );
6816
			} else {
6817
				fs_require_once_template( 'pricing.php', $vars );
6818
			}
6819
		}
6820
6821
		#region Contact Us ------------------------------------------------------------------
6822
6823
		/**
6824
		 * Render contact-us page.
6825
		 *
6826
		 * @author Vova Feldman (@svovaf)
6827
		 * @since  1.0.3
6828
		 */
6829
		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...
6830
			$this->_logger->entrance();
6831
6832
			$vars = array( 'slug' => $this->_slug );
6833
			fs_require_once_template( 'contact.php', $vars );
6834
		}
6835
6836
		#endregion ------------------------------------------------------------------
6837
6838
		/**
6839
		 * Hide all admin notices to prevent distractions.
6840
		 *
6841
		 * @author Vova Feldman (@svovaf)
6842
		 * @since  1.0.3
6843
		 *
6844
		 * @uses   remove_all_actions()
6845
		 */
6846
		function _hide_admin_notices() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6847
			remove_all_actions( 'admin_notices' );
6848
			remove_all_actions( 'network_admin_notices' );
6849
			remove_all_actions( 'all_admin_notices' );
6850
			remove_all_actions( 'user_admin_notices' );
6851
		}
6852
6853
		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...
6854
			$this->_hide_admin_notices();
6855
6856
			// Hide footer.
6857
			echo '<style>#wpfooter { display: none !important; }</style>';
6858
		}
6859
6860
		/**
6861
		 * Attach to admin_head hook to hide all admin notices.
6862
		 *
6863
		 * @author Vova Feldman (@svovaf)
6864
		 * @since  1.0.3
6865
		 */
6866
		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...
6867
			add_action( 'admin_head', array( &$this, '_clean_admin_content_section_hook' ) );
6868
		}
6869
6870
		/* CSS & JavaScript
6871
		------------------------------------------------------------------------------------------------------------------*/
6872
		/*		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...
6873
					$url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src );
6874
6875
					$this->_logger->entrance( 'script = ' . $url );
6876
6877
					wp_enqueue_script( $handle, $url );
6878
				}*/
6879
6880
		/* SDK
6881
		------------------------------------------------------------------------------------------------------------------*/
6882
		private $_user_api;
6883
6884
		/**
6885
		 *
6886
		 * @author Vova Feldman (@svovaf)
6887
		 * @since  1.0.2
6888
		 *
6889
		 * @param bool $flush
6890
		 *
6891
		 * @return FS_Api
6892
		 */
6893
		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...
6894
			if ( ! isset( $this->_user_api ) || $flush ) {
6895
				$this->_user_api = FS_Api::instance(
6896
					$this->_slug,
6897
					'user',
6898
					$this->_user->id,
6899
					$this->_user->public_key,
6900
					! $this->is_live(),
6901
					$this->_user->secret_key
6902
				);
6903
			}
6904
6905
			return $this->_user_api;
6906
		}
6907
6908
		private $_site_api;
6909
6910
		/**
6911
		 *
6912
		 * @author Vova Feldman (@svovaf)
6913
		 * @since  1.0.2
6914
		 *
6915
		 * @param bool $flush
6916
		 *
6917
		 * @return FS_Api
6918
		 */
6919
		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...
6920
			if ( ! isset( $this->_site_api ) || $flush ) {
6921
				$this->_site_api = FS_Api::instance(
6922
					$this->_slug,
6923
					'install',
6924
					$this->_site->id,
6925
					$this->_site->public_key,
6926
					! $this->is_live(),
6927
					$this->_site->secret_key
6928
				);
6929
			}
6930
6931
			return $this->_site_api;
6932
		}
6933
6934
		private $_plugin_api;
6935
6936
		/**
6937
		 * Get plugin public API scope.
6938
		 *
6939
		 * @author Vova Feldman (@svovaf)
6940
		 * @since  1.0.7
6941
		 *
6942
		 * @return FS_Api
6943
		 */
6944
		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...
6945
			if ( ! isset( $this->_plugin_api ) ) {
6946
				$this->_plugin_api = FS_Api::instance(
6947
					$this->_slug,
6948
					'plugin',
6949
					$this->_plugin->id,
6950
					$this->_plugin->public_key,
6951
					! $this->is_live()
6952
				);
6953
			}
6954
6955
			return $this->_plugin_api;
6956
		}
6957
6958
		/**
6959
		 * Get site API scope object (fallback to public plugin scope when not registered).
6960
		 *
6961
		 * @author Vova Feldman (@svovaf)
6962
		 * @since  1.0.7
6963
		 *
6964
		 * @return FS_Api
6965
		 */
6966
		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...
6967
			return $this->is_registered() ?
6968
				$this->get_api_site_scope() :
6969
				$this->get_api_plugin_scope();
6970
		}
6971
6972
		/**
6973
		 * Show trial promotional notice (if any trial exist).
6974
		 *
6975
		 * @author Vova Feldman (@svovaf)
6976
		 * @since  1.0.9
6977
		 *
6978
		 * @param $plans
6979
		 */
6980
		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...
6981
			$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...
6982
		}
6983
6984
		/**
6985
		 * Show trial promotional notice (if any trial exist).
6986
		 *
6987
		 * @author Vova Feldman (@svovaf)
6988
		 * @since  1.0.9
6989
		 */
6990
		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...
6991
			// Check if trial already utilized.
6992
			if ( $this->_site->is_trial_utilized() ) {
6993
				return;
6994
			}
6995
6996
			// Check if already paying.
6997
			if ( $this->is_paying() ) {
6998
				return;
6999
			}
7000
7001
			// Check if trial message is already shown.
7002
			if ( $this->_admin_notices->has_sticky( 'trial_promotion' ) ) {
7003
				return;
7004
			}
7005
7006
			$trial_plans       = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
7007
			$trial_plans_count = count( $trial_plans );
7008
7009
			// Check if any of the plans contains trial.
7010
			if ( 0 === $trial_plans_count ) {
7011
				return;
7012
			}
7013
7014
			/**
7015
			 * @var FS_Plugin_Plan $paid_plan
7016
			 */
7017
			$paid_plan            = $trial_plans[0];
7018
			$require_subscription = $paid_plan->is_require_subscription;
7019
			$upgrade_url          = $this->get_trial_url();
7020
			$cc_string            = $require_subscription ?
7021
				sprintf( __fs( 'no-commitment-for-x-days' ), $paid_plan->trial_period ) :
7022
				__fs( 'no-cc-required' ) . '!';
7023
7024
7025
			$total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 );
7026
7027
			if ( $total_paid_plans === $trial_plans_count ) {
7028
				// All paid plans have trials.
7029
				$message = sprintf(
7030
					__fs( 'hey' ) . '! ' . __fs( 'trial-x-promotion-message' ),
7031
					sprintf( '<b>%s</b>', $this->get_plugin_name() ),
7032
					strtolower( __fs( 'awesome' ) ),
7033
					$paid_plan->trial_period
7034
				);
7035
			} else {
7036
				$plans_string = '';
7037
				for ( $i = 0; $i < $trial_plans_count; $i ++ ) {
7038
					$plans_string .= sprintf( '<a href="%s">%s</a>', $upgrade_url, $trial_plans[ $i ]->title );
7039
7040
					if ( $i < $trial_plans_count - 2 ) {
7041
						$plans_string .= ', ';
7042
					} else if ( $i == $trial_plans_count - 2 ) {
7043
						$plans_string .= ' and ';
7044
					}
7045
				}
7046
7047
				// Not all paid plans have trials.
7048
				$message = sprintf(
7049
					__fs( 'hey' ) . '! ' . __fs( 'trial-x-promotion-message' ),
7050
					sprintf( '<b>%s</b>', $this->get_plugin_name() ),
7051
					$plans_string,
7052
					$paid_plan->trial_period
7053
				);
7054
			}
7055
7056
			$message .= ' ' . $cc_string;
7057
7058
			// Add start trial button.
7059
			$message .= ' ' . sprintf(
7060
					'<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
7061
					$upgrade_url,
7062
					__fs( 'start-free-trial' )
7063
				);
7064
7065
			$this->_admin_notices->add_sticky(
7066
				$this->apply_filters( 'trial_promotion_message', $message ),
7067
				'trial_promotion',
7068
				'',
7069
				'promotion'
7070
			);
7071
7072
			$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...
7073
		}
7074
7075
		/* Action Links
7076
		------------------------------------------------------------------------------------------------------------------*/
7077
		private $_action_links_hooked = false;
7078
		private $_action_links = array();
7079
7080
		/**
7081
		 * @author Vova Feldman (@svovaf)
7082
		 * @since  1.0.0
7083
		 *
7084
		 * @return bool
7085
		 */
7086
		private function is_plugin_action_links_hooked() {
7087
			$this->_logger->entrance( json_encode( $this->_action_links_hooked ) );
7088
7089
			return $this->_action_links_hooked;
7090
		}
7091
7092
		/**
7093
		 * Hook to plugin action links filter.
7094
		 *
7095
		 * @author Vova Feldman (@svovaf)
7096
		 * @since  1.0.0
7097
		 */
7098
		private function hook_plugin_action_links() {
7099
			$this->_logger->entrance();
7100
7101
			$this->_action_links_hooked = true;
7102
7103
			$this->_logger->log( 'Adding action links hooks.' );
7104
7105
			// Add action link to settings page.
7106
			add_filter( 'plugin_action_links_' . $this->_plugin_basename, array(
7107
				&$this,
7108
				'_modify_plugin_action_links_hook'
7109
			), 10, 2 );
7110
			add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array(
7111
				&$this,
7112
				'_modify_plugin_action_links_hook'
7113
			), 10, 2 );
7114
		}
7115
7116
		/**
7117
		 * Add plugin action link.
7118
		 *
7119
		 * @author Vova Feldman (@svovaf)
7120
		 * @since  1.0.0
7121
		 *
7122
		 * @param      $label
7123
		 * @param      $url
7124
		 * @param bool $external
7125
		 * @param int  $priority
7126
		 * @param bool $key
7127
		 */
7128
		function add_plugin_action_link( $label, $url, $external = false, $priority = 10, $key = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7129
			$this->_logger->entrance();
7130
7131
			if ( ! isset( $this->_action_links[ $priority ] ) ) {
7132
				$this->_action_links[ $priority ] = array();
7133
			}
7134
7135
			if ( false === $key ) {
7136
				$key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) );
7137
			}
7138
7139
			$this->_action_links[ $priority ][] = array(
7140
				'label'    => $label,
7141
				'href'     => $url,
7142
				'key'      => $key,
7143
				'external' => $external
7144
			);
7145
		}
7146
7147
		/**
7148
		 * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection.
7149
		 *
7150
		 * @author Vova Feldman (@svovaf)
7151
		 * @since  1.0.0
7152
		 */
7153
		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...
7154
			$this->_logger->entrance();
7155
7156
			if ( $this->is_registered() ) {
7157
				if ( ! $this->is_paying() && $this->has_paid_plan() ) {
7158
					$this->add_plugin_action_link(
7159
						__fs( 'upgrade' ),
7160
						$this->get_upgrade_url(),
7161
						false,
7162
						20,
7163
						'upgrade'
7164
					);
7165
				}
7166
7167
				if ( $this->_has_addons() ) {
7168
					$this->add_plugin_action_link(
7169
						__fs( 'add-ons' ),
7170
						$this->_get_admin_page_url( 'addons' ),
7171
						false,
7172
						10,
7173
						'addons'
7174
					);
7175
				}
7176
			}
7177
		}
7178
7179
		/**
7180
		 * Forward page to activation page.
7181
		 *
7182
		 * @author Vova Feldman (@svovaf)
7183
		 * @since  1.0.3
7184
		 */
7185
		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...
7186
			$url       = false;
7187
			$plugin_fs = false;
7188
7189
			if ( ! $this->is_addon() ) {
7190
				$first_time_path = $this->_menu->get_first_time_path();
7191
				$plugin_fs       = $this;
7192
				$url             = $plugin_fs->is_activation_mode() ?
7193
					$plugin_fs->get_activation_url() :
7194
					( empty( $first_time_path ) ?
7195
						$this->_get_admin_page_url() :
7196
						$first_time_path );
7197
			} else {
7198
				if ( $this->is_parent_plugin_installed() ) {
7199
					$plugin_fs = self::get_parent_instance();
7200
				}
7201
7202
				if ( is_object( $plugin_fs ) ) {
7203
					if ( ! $plugin_fs->is_registered() ) {
7204
						// Forward to parent plugin connect when parent not registered.
7205
						$url = $plugin_fs->get_activation_url();
7206
					} else {
7207
						// Forward to account page.
7208
						$url = $plugin_fs->_get_admin_page_url( 'account' );
7209
					}
7210
				}
7211
			}
7212
7213
			if ( is_string( $url ) ) {
7214
				fs_redirect( $url );
7215
				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...
7216
			}
7217
		}
7218
7219
		/**
7220
		 * Modify plugin's page action links collection.
7221
		 *
7222
		 * @author Vova Feldman (@svovaf)
7223
		 * @since  1.0.0
7224
		 *
7225
		 * @param array $links
7226
		 * @param       $file
7227
		 *
7228
		 * @return array
7229
		 */
7230
		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...
7231
			$this->_logger->entrance();
7232
7233
			ksort( $this->_action_links );
7234
7235
			foreach ( $this->_action_links as $new_links ) {
7236
				foreach ( $new_links as $link ) {
7237
					$links[ $link['key'] ] = '<a href="' . $link['href'] . '"' . ( $link['external'] ? ' target="_blank"' : '' ) . '>' . $link['label'] . '</a>';
7238
				}
7239
			}
7240
7241
			/*
7242
			 * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link.
7243
			 *
7244
			 * If user is paying or in trial and have the free version installed,
7245
			 * assume that the deactivation is for the upgrade process, so this is not needed.
7246
			 */
7247
			if ( ! $this->is_paying_or_trial() || $this->is_premium() ) {
7248
				if ( isset( $links['deactivate'] ) ) {
7249
					$links['deactivate'] .= '<i class="fs-slug" data-slug="' . $this->_slug . '"></i>';
7250
				}
7251
			}
7252
7253
			return $links;
7254
		}
7255
7256
		/**
7257
		 * Adds admin message.
7258
		 *
7259
		 * @author Vova Feldman (@svovaf)
7260
		 * @since  1.0.4
7261
		 *
7262
		 * @param string $message
7263
		 * @param string $title
7264
		 * @param string $type
7265
		 */
7266
		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...
7267
			$this->_admin_notices->add( $message, $title, $type );
7268
		}
7269
7270
		/**
7271
		 * Adds sticky admin message.
7272
		 *
7273
		 * @author Vova Feldman (@svovaf)
7274
		 * @since  1.1.0
7275
		 *
7276
		 * @param string $message
7277
		 * @param string $id
7278
		 * @param string $title
7279
		 * @param string $type
7280
		 */
7281
		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...
7282
			$this->_admin_notices->add_sticky( $message, $id, $title, $type );
7283
		}
7284
7285
		/* Plugin Auto-Updates (@since 1.0.4)
7286
		------------------------------------------------------------------------------------------------------------------*/
7287
		/**
7288
		 * @var string[]
7289
		 */
7290
		private static $_auto_updated_plugins;
7291
7292
		/**
7293
		 * @todo   TEST IF IT WORKS!!!
7294
		 *
7295
		 * Include plugins for automatic updates based on stored settings.
7296
		 *
7297
		 * @see    http://wordpress.stackexchange.com/questions/131394/how-do-i-exclude-plugins-from-getting-automatically-updated/131404#131404
7298
		 *
7299
		 * @author Vova Feldman (@svovaf)
7300
		 * @since  1.0.4
7301
		 *
7302
		 * @param bool   $update Whether to update (not used for plugins)
7303
		 * @param object $item   The plugin's info
7304
		 *
7305
		 * @return bool
7306
		 */
7307
		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...
7308
			// Before version 3.8.2 the $item was the file name of the plugin,
7309
			// while in 3.8.2 statistics were added (https://core.trac.wordpress.org/changeset/27905).
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
7310
			$by_slug = ( (int) str_replace( '.', '', get_bloginfo( 'version' ) ) >= 382 );
7311
7312
			if ( ! isset( self::$_auto_updated_plugins ) ) {
7313
				$plugins = self::$_accounts->get_option( 'plugins', array() );
7314
7315
				$identifiers = array();
7316
				foreach ( $plugins as $p ) {
7317
					/**
7318
					 * @var FS_Plugin $p
7319
					 */
7320
					if ( isset( $p->auto_update ) && $p->auto_update ) {
7321
						$identifiers[] = ( $by_slug ? $p->slug : plugin_basename( $p->file ) );
7322
					}
7323
				}
7324
7325
				self::$_auto_updated_plugins = $identifiers;
7326
			}
7327
7328
			if ( in_array( $by_slug ? $item->slug : $item, self::$_auto_updated_plugins ) ) {
7329
				return true;
7330
			}
7331
7332
			// Pass update decision to next filters
7333
			return $update;
7334
		}
7335
7336
		#region Versioning ------------------------------------------------------------------
7337
7338
		/**
7339
		 * Check if Freemius in SDK upgrade mode.
7340
		 *
7341
		 * @author Vova Feldman (@svovaf)
7342
		 * @since  1.0.9
7343
		 *
7344
		 * @return bool
7345
		 */
7346
		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...
7347
			return isset( $this->_storage->sdk_upgrade_mode ) ?
7348
				$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...
7349
				false;
7350
		}
7351
7352
		/**
7353
		 * Turn SDK upgrade mode off.
7354
		 *
7355
		 * @author Vova Feldman (@svovaf)
7356
		 * @since  1.0.9
7357
		 *
7358
		 * @return bool
7359
		 */
7360
		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...
7361
			$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...
7362
		}
7363
7364
		/**
7365
		 * Check if plugin upgrade mode.
7366
		 *
7367
		 * @author Vova Feldman (@svovaf)
7368
		 * @since  1.0.9
7369
		 *
7370
		 * @return bool
7371
		 */
7372
		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...
7373
			return isset( $this->_storage->plugin_upgrade_mode ) ?
7374
				$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...
7375
				false;
7376
		}
7377
7378
		/**
7379
		 * Turn plugin upgrade mode off.
7380
		 *
7381
		 * @author Vova Feldman (@svovaf)
7382
		 * @since  1.0.9
7383
		 *
7384
		 * @return bool
7385
		 */
7386
		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...
7387
			$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...
7388
		}
7389
7390
		#endregion ------------------------------------------------------------------
7391
7392
		#region Marketing ------------------------------------------------------------------
7393
7394
		/**
7395
		 * Check if current user purchased any other plugins before.
7396
		 *
7397
		 * @author Vova Feldman (@svovaf)
7398
		 * @since  1.0.9
7399
		 *
7400
		 * @return bool
7401
		 */
7402
		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...
7403
			// TODO: Implement has_purchased_before() method.
7404
		}
7405
7406
		/**
7407
		 * Check if current user classified as an agency.
7408
		 *
7409
		 * @author Vova Feldman (@svovaf)
7410
		 * @since  1.0.9
7411
		 *
7412
		 * @return bool
7413
		 */
7414
		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...
7415
			// TODO: Implement is_agency() method.
7416
		}
7417
7418
		/**
7419
		 * Check if current user classified as a developer.
7420
		 *
7421
		 * @author Vova Feldman (@svovaf)
7422
		 * @since  1.0.9
7423
		 *
7424
		 * @return bool
7425
		 */
7426
		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...
7427
			// TODO: Implement is_developer() method.
7428
		}
7429
7430
		/**
7431
		 * Check if current user classified as a business.
7432
		 *
7433
		 * @author Vova Feldman (@svovaf)
7434
		 * @since  1.0.9
7435
		 *
7436
		 * @return bool
7437
		 */
7438
		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...
7439
			// TODO: Implement is_business() method.
7440
		}
7441
7442
		#endregion ------------------------------------------------------------------
7443
	}