PluginProbe ʕ •ᴥ•ʔ
Code Manager / 1.0.28
Code Manager v1.0.28
1.0.47 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.3 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 1.0.39 1.0.4 1.0.40 1.0.41 1.0.42 1.0.43 1.0.44 1.0.45 1.0.46 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9
code-manager / freemius / includes / class-fs-storage.php
code-manager / freemius / includes Last commit date
customizer 2 years ago debug 2 years ago entities 2 years ago managers 2 years ago sdk 2 years ago supplements 2 years ago class-freemius-abstract.php 2 years ago class-freemius.php 2 years ago class-fs-admin-notices.php 2 years ago class-fs-api.php 2 years ago class-fs-lock.php 2 years ago class-fs-logger.php 2 years ago class-fs-options.php 2 years ago class-fs-plugin-updater.php 2 years ago class-fs-security.php 2 years ago class-fs-storage.php 2 years ago class-fs-user-lock.php 2 years ago fs-core-functions.php 2 years ago fs-essential-functions.php 2 years ago fs-html-escaping-functions.php 2 years ago fs-plugin-info-dialog.php 2 years ago index.php 2 years ago l10n.php 2 years ago
class-fs-storage.php
559 lines
1 <?php
2 /**
3 * @package Freemius
4 * @copyright Copyright (c) 2015, Freemius, Inc.
5 * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6 * @since 1.2.3
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * Class FS_Storage
15 *
16 * A wrapper class for handling network level and single site level storage.
17 *
18 * @property bool $is_network_activation
19 * @property int $network_install_blog_id
20 * @property bool|null $is_extensions_tracking_allowed
21 * @property bool|null $is_diagnostic_tracking_allowed
22 * @property object $sync_cron
23 */
24 class FS_Storage {
25 /**
26 * @var FS_Storage[]
27 */
28 private static $_instances = array();
29 /**
30 * @var FS_Key_Value_Storage Site level storage.
31 */
32 private $_storage;
33
34 /**
35 * @var FS_Key_Value_Storage Network level storage.
36 */
37 private $_network_storage;
38
39 /**
40 * @var string
41 */
42 private $_module_type;
43
44 /**
45 * @var string
46 */
47 private $_module_slug;
48
49 /**
50 * @var int The ID of the blog that is associated with the current site level options.
51 */
52 private $_blog_id = 0;
53
54 /**
55 * @var bool
56 */
57 private $_is_multisite;
58
59 /**
60 * @var bool
61 */
62 private $_is_network_active = false;
63
64 /**
65 * @var bool
66 */
67 private $_is_delegated_connection = false;
68
69 /**
70 * @var array {
71 * @key string Option name.
72 * @value int If 0 store on the network level. If 1, store on the network level only if module was network level activated. If 2, store on the network level only if network activated and NOT delegated the connection.
73 * }
74 */
75 private static $_NETWORK_OPTIONS_MAP;
76
77 const OPTION_LEVEL_UNDEFINED = -1;
78 // The option should be stored on the network level.
79 const OPTION_LEVEL_NETWORK = 0;
80 // The option should be stored on the network level when the plugin is network-activated.
81 const OPTION_LEVEL_NETWORK_ACTIVATED = 1;
82 // The option should be stored on the network level when the plugin is network-activated and the opt-in connection was NOT delegated to the sub-site admin.
83 const OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED = 2;
84 // The option should be stored on the site level.
85 const OPTION_LEVEL_SITE = 3;
86
87 /**
88 * @author Leo Fajardo (@leorw)
89 *
90 * @param string $module_type
91 * @param string $slug
92 *
93 * @return FS_Storage
94 */
95 static function instance( $module_type, $slug ) {
96 $key = $module_type . ':' . $slug;
97
98 if ( ! isset( self::$_instances[ $key ] ) ) {
99 self::$_instances[ $key ] = new FS_Storage( $module_type, $slug );
100 }
101
102 return self::$_instances[ $key ];
103 }
104
105 /**
106 * @author Leo Fajardo (@leorw)
107 *
108 * @param string $module_type
109 * @param string $slug
110 */
111 private function __construct( $module_type, $slug ) {
112 $this->_module_type = $module_type;
113 $this->_module_slug = $slug;
114 $this->_is_multisite = is_multisite();
115
116 if ( $this->_is_multisite ) {
117 $this->_blog_id = get_current_blog_id();
118 $this->_network_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, true );
119 }
120
121 $this->_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, $this->_blog_id );
122 }
123
124 /**
125 * Tells this storage wrapper class that the context plugin is network active. This flag will affect how values
126 * are retrieved/stored from/into the storage.
127 *
128 * @author Leo Fajardo (@leorw)
129 *
130 * @param bool $is_network_active
131 * @param bool $is_delegated_connection
132 */
133 function set_network_active( $is_network_active = true, $is_delegated_connection = false ) {
134 $this->_is_network_active = $is_network_active;
135 $this->_is_delegated_connection = $is_delegated_connection;
136 }
137
138 /**
139 * Switch the context of the site level storage manager.
140 *
141 * @author Vova Feldman (@svovaf)
142 * @since 2.0.0
143 *
144 * @param int $blog_id
145 */
146 function set_site_blog_context( $blog_id ) {
147 $this->_storage = $this->get_site_storage( $blog_id );
148 $this->_blog_id = $blog_id;
149 }
150
151 /**
152 * @author Leo Fajardo (@leorw)
153 *
154 * @param string $key
155 * @param mixed $value
156 * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
157 * @param int $option_level Since 2.5.1
158 * @param bool $flush
159 */
160 function store(
161 $key,
162 $value,
163 $network_level_or_blog_id = null,
164 $option_level = self::OPTION_LEVEL_UNDEFINED,
165 $flush = true
166 ) {
167 if ( $this->should_use_network_storage( $key, $network_level_or_blog_id, $option_level ) ) {
168 $this->_network_storage->store( $key, $value, $flush );
169 } else {
170 $storage = $this->get_site_storage( $network_level_or_blog_id );
171 $storage->store( $key, $value, $flush );
172 }
173 }
174
175 /**
176 * @author Leo Fajardo (@leorw)
177 *
178 * @param bool $store
179 * @param string[] $exceptions Set of keys to keep and not clear.
180 * @param int|null|bool $network_level_or_blog_id
181 */
182 function clear_all( $store = true, $exceptions = array(), $network_level_or_blog_id = null ) {
183 if ( ! $this->_is_multisite ||
184 false === $network_level_or_blog_id ||
185 is_null( $network_level_or_blog_id ) ||
186 is_numeric( $network_level_or_blog_id )
187 ) {
188 $storage = $this->get_site_storage( $network_level_or_blog_id );
189 $storage->clear_all( $store, $exceptions );
190 }
191
192 if ( $this->_is_multisite &&
193 ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) )
194 ) {
195 $this->_network_storage->clear_all( $store, $exceptions );
196 }
197 }
198
199 /**
200 * @author Leo Fajardo (@leorw)
201 *
202 * @param string $key
203 * @param bool $store
204 * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
205 */
206 function remove( $key, $store = true, $network_level_or_blog_id = null ) {
207 if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) {
208 $this->_network_storage->remove( $key, $store );
209 } else {
210 $storage = $this->get_site_storage( $network_level_or_blog_id );
211 $storage->remove( $key, $store );
212 }
213 }
214
215 /**
216 * @author Leo Fajardo (@leorw)
217 *
218 * @param string $key
219 * @param mixed $default
220 * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
221 * @param int $option_level Since 2.5.1
222 *
223 * @return mixed
224 */
225 function get(
226 $key,
227 $default = false,
228 $network_level_or_blog_id = null,
229 $option_level = self::OPTION_LEVEL_UNDEFINED
230 ) {
231 if ( $this->should_use_network_storage( $key, $network_level_or_blog_id, $option_level ) ) {
232 return $this->_network_storage->get( $key, $default );
233 } else {
234 $storage = $this->get_site_storage( $network_level_or_blog_id );
235
236 return $storage->get( $key, $default );
237 }
238 }
239
240 /**
241 * Multisite activated:
242 * true: Save network storage.
243 * int: Save site specific storage.
244 * false|0: Save current site storage.
245 * null: Save network and current site storage.
246 * Site level activated:
247 * Save site storage.
248 *
249 * @author Vova Feldman (@svovaf)
250 * @since 2.0.0
251 *
252 * @param bool|int|null $network_level_or_blog_id
253 */
254 function save( $network_level_or_blog_id = null ) {
255 if ( $this->_is_network_active &&
256 ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) )
257 ) {
258 $this->_network_storage->save();
259 }
260
261 if ( ! $this->_is_network_active || true !== $network_level_or_blog_id ) {
262 $storage = $this->get_site_storage( $network_level_or_blog_id );
263 $storage->save();
264 }
265 }
266
267 /**
268 * @author Vova Feldman (@svovaf)
269 * @since 2.0.0
270 *
271 * @return string
272 */
273 function get_module_slug() {
274 return $this->_module_slug;
275 }
276
277 /**
278 * @author Vova Feldman (@svovaf)
279 * @since 2.0.0
280 *
281 * @return string
282 */
283 function get_module_type() {
284 return $this->_module_type;
285 }
286
287 /**
288 * Migration script to the new storage data structure that is network compatible.
289 *
290 * IMPORTANT:
291 * This method should be executed only after it is determined if this is a network
292 * level compatible product activation.
293 *
294 * @author Vova Feldman (@svovaf)
295 * @since 2.0.0
296 */
297 function migrate_to_network() {
298 if ( ! $this->_is_multisite ) {
299 return;
300 }
301
302 $updated = false;
303
304 if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) {
305 self::load_network_options_map();
306 }
307
308 foreach ( self::$_NETWORK_OPTIONS_MAP as $option => $storage_level ) {
309 if ( ! $this->is_multisite_option( $option ) ) {
310 continue;
311 }
312
313 if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) {
314 // Migrate option to the network storage.
315 $this->_network_storage->store( $option, $this->_storage->{$option}, false );
316
317 $updated = true;
318 }
319 }
320
321 if ( ! $updated ) {
322 return;
323 }
324
325 // Update network level storage.
326 $this->_network_storage->save();
327 // $this->_storage->save();
328 }
329
330 #--------------------------------------------------------------------------------
331 #region Helper Methods
332 #--------------------------------------------------------------------------------
333
334 /**
335 * We don't want to load the map right away since it's not even needed in a non-MS environment.
336 *
337 * Example:
338 * array(
339 * 'option1' => 0, // Means that the option should always be stored on the network level.
340 * 'option2' => 1, // Means that the option should be stored on the network level only when the module was network level activated.
341 * 'option2' => 2, // Means that the option should be stored on the network level only when the module was network level activated AND the connection was NOT delegated.
342 * 'option3' => 3, // Means that the option should always be stored on the site level.
343 * )
344 *
345 * @author Vova Feldman (@svovaf)
346 * @since 2.0.0
347 */
348 private static function load_network_options_map() {
349 self::$_NETWORK_OPTIONS_MAP = array(
350 // Network level options.
351 'affiliate_application_data' => self::OPTION_LEVEL_NETWORK,
352 'beta_data' => self::OPTION_LEVEL_NETWORK,
353 'connectivity_test' => self::OPTION_LEVEL_NETWORK,
354 'handle_gdpr_admin_notice' => self::OPTION_LEVEL_NETWORK,
355 'has_trial_plan' => self::OPTION_LEVEL_NETWORK,
356 'install_sync_timestamp' => self::OPTION_LEVEL_NETWORK,
357 'install_sync_cron' => self::OPTION_LEVEL_NETWORK,
358 'is_anonymous_ms' => self::OPTION_LEVEL_NETWORK,
359 'is_network_activated' => self::OPTION_LEVEL_NETWORK,
360 'is_on' => self::OPTION_LEVEL_NETWORK,
361 'is_plugin_new_install' => self::OPTION_LEVEL_NETWORK,
362 'network_install_blog_id' => self::OPTION_LEVEL_NETWORK,
363 'pending_sites_info' => self::OPTION_LEVEL_NETWORK,
364 'plugin_last_version' => self::OPTION_LEVEL_NETWORK,
365 'plugin_main_file' => self::OPTION_LEVEL_NETWORK,
366 'plugin_version' => self::OPTION_LEVEL_NETWORK,
367 'sdk_downgrade_mode' => self::OPTION_LEVEL_NETWORK,
368 'sdk_last_version' => self::OPTION_LEVEL_NETWORK,
369 'sdk_upgrade_mode' => self::OPTION_LEVEL_NETWORK,
370 'sdk_version' => self::OPTION_LEVEL_NETWORK,
371 'sticky_optin_added_ms' => self::OPTION_LEVEL_NETWORK,
372 'subscriptions' => self::OPTION_LEVEL_NETWORK,
373 'sync_timestamp' => self::OPTION_LEVEL_NETWORK,
374 'sync_cron' => self::OPTION_LEVEL_NETWORK,
375 'was_plugin_loaded' => self::OPTION_LEVEL_NETWORK,
376 'network_user_id' => self::OPTION_LEVEL_NETWORK,
377 'plugin_upgrade_mode' => self::OPTION_LEVEL_NETWORK,
378 'plugin_downgrade_mode' => self::OPTION_LEVEL_NETWORK,
379 'is_network_connected' => self::OPTION_LEVEL_NETWORK,
380 /**
381 * Special flag that is used when a super-admin upgrades to the new version of the SDK that supports network level integration, when the connection decision wasn't made for all the sites in the network.
382 */
383 'is_network_activation' => self::OPTION_LEVEL_NETWORK,
384 'license_migration' => self::OPTION_LEVEL_NETWORK,
385
386 // When network activated, then network level.
387 'install_timestamp' => self::OPTION_LEVEL_NETWORK_ACTIVATED,
388 'prev_is_premium' => self::OPTION_LEVEL_NETWORK_ACTIVATED,
389 'require_license_activation' => self::OPTION_LEVEL_NETWORK_ACTIVATED,
390
391 // If not network activated OR delegated, then site level.
392 'activation_timestamp' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
393 'expired_license_notice_shown' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
394 'is_whitelabeled' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
395 'last_license_key' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
396 'last_license_user_id' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
397 'prev_user_id' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
398 'sticky_optin_added' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
399 'uninstall_reason' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
400 'is_pending_activation' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
401 'pending_license_key' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
402
403 // Site level options.
404 'is_anonymous' => self::OPTION_LEVEL_SITE,
405 'clone_id' => self::OPTION_LEVEL_SITE,
406 );
407 }
408
409 /**
410 * This method will and should only be executed when is_multisite() is true.
411 *
412 * @author Vova Feldman (@svovaf)
413 * @since 2.0.0
414 *
415 * @param string $key
416 * @param int $option_level Since 2.5.1
417 *
418 * @return bool
419 */
420 private function is_multisite_option( $key, $option_level = self::OPTION_LEVEL_UNDEFINED ) {
421 if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) {
422 self::load_network_options_map();
423 }
424
425 if (
426 self::OPTION_LEVEL_UNDEFINED === $option_level &&
427 isset( self::$_NETWORK_OPTIONS_MAP[ $key ] )
428 ) {
429 $option_level = self::$_NETWORK_OPTIONS_MAP[ $key ];
430 }
431
432 if ( self::OPTION_LEVEL_UNDEFINED === $option_level ) {
433 // Option not found -> use site level storage.
434 return false;
435 }
436
437 if ( self::OPTION_LEVEL_NETWORK === $option_level ) {
438 // Option found and set to always use the network level storage on a multisite.
439 return true;
440 }
441
442 if ( self::OPTION_LEVEL_SITE === $option_level ) {
443 // Option found and set to always use the site level storage on a multisite.
444 return false;
445 }
446
447 if ( ! $this->_is_network_active ) {
448 return false;
449 }
450
451 if ( self::OPTION_LEVEL_NETWORK_ACTIVATED === $option_level ) {
452 // Network activated.
453 return true;
454 }
455
456 if (
457 self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED === $option_level &&
458 ! $this->_is_delegated_connection
459 ) {
460 // Network activated and not delegated.
461 return true;
462 }
463
464 return false;
465 }
466
467 /**
468 * @author Leo Fajardo
469 *
470 * @param string $key
471 * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
472 * @param int $option_level Since 2.5.1
473 *
474 * @return bool
475 */
476 private function should_use_network_storage(
477 $key,
478 $network_level_or_blog_id = null,
479 $option_level = self::OPTION_LEVEL_UNDEFINED
480 ) {
481 if ( ! $this->_is_multisite ) {
482 // Not a multisite environment.
483 return false;
484 }
485
486 if ( is_numeric( $network_level_or_blog_id ) ) {
487 // Explicitly asked to use a specified blog storage.
488 return false;
489 }
490
491 if ( is_bool( $network_level_or_blog_id ) ) {
492 // Explicitly specified whether it should use the network or blog level storage.
493 return $network_level_or_blog_id;
494 }
495
496 // Determine which storage to use based on the option.
497 return $this->is_multisite_option( $key, $option_level );
498 }
499
500 /**
501 * @author Vova Feldman (@svovaf)
502 * @since 2.0.0
503 *
504 * @param int $blog_id
505 *
506 * @return \FS_Key_Value_Storage
507 */
508 private function get_site_storage( $blog_id = 0 ) {
509 if ( ! is_numeric( $blog_id ) ||
510 $blog_id == $this->_blog_id ||
511 0 == $blog_id
512 ) {
513 return $this->_storage;
514 }
515
516 return FS_Key_Value_Storage::instance(
517 $this->_module_type . '_data',
518 $this->_storage->get_secondary_id(),
519 $blog_id
520 );
521 }
522
523 #endregion
524
525 #--------------------------------------------------------------------------------
526 #region Magic methods
527 #--------------------------------------------------------------------------------
528
529 function __set( $k, $v ) {
530 if ( $this->should_use_network_storage( $k ) ) {
531 $this->_network_storage->{$k} = $v;
532 } else {
533 $this->_storage->{$k} = $v;
534 }
535 }
536
537 function __isset( $k ) {
538 return $this->should_use_network_storage( $k ) ?
539 isset( $this->_network_storage->{$k} ) :
540 isset( $this->_storage->{$k} );
541 }
542
543 function __unset( $k ) {
544 if ( $this->should_use_network_storage( $k ) ) {
545 unset( $this->_network_storage->{$k} );
546 } else {
547 unset( $this->_storage->{$k} );
548 }
549 }
550
551 function __get( $k ) {
552 return $this->should_use_network_storage( $k ) ?
553 $this->_network_storage->{$k} :
554 $this->_storage->{$k};
555 }
556
557 #endregion
558 }
559