PluginProbe ʕ •ᴥ•ʔ
Code Manager / 1.0.35
Code Manager v1.0.35
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-garbage-collector.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
560 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 'last_load_timestamp' => self::OPTION_LEVEL_NETWORK,
363 'network_install_blog_id' => self::OPTION_LEVEL_NETWORK,
364 'pending_sites_info' => self::OPTION_LEVEL_NETWORK,
365 'plugin_last_version' => self::OPTION_LEVEL_NETWORK,
366 'plugin_main_file' => self::OPTION_LEVEL_NETWORK,
367 'plugin_version' => self::OPTION_LEVEL_NETWORK,
368 'sdk_downgrade_mode' => self::OPTION_LEVEL_NETWORK,
369 'sdk_last_version' => self::OPTION_LEVEL_NETWORK,
370 'sdk_upgrade_mode' => self::OPTION_LEVEL_NETWORK,
371 'sdk_version' => self::OPTION_LEVEL_NETWORK,
372 'sticky_optin_added_ms' => self::OPTION_LEVEL_NETWORK,
373 'subscriptions' => self::OPTION_LEVEL_NETWORK,
374 'sync_timestamp' => self::OPTION_LEVEL_NETWORK,
375 'sync_cron' => self::OPTION_LEVEL_NETWORK,
376 'was_plugin_loaded' => self::OPTION_LEVEL_NETWORK,
377 'network_user_id' => self::OPTION_LEVEL_NETWORK,
378 'plugin_upgrade_mode' => self::OPTION_LEVEL_NETWORK,
379 'plugin_downgrade_mode' => self::OPTION_LEVEL_NETWORK,
380 'is_network_connected' => self::OPTION_LEVEL_NETWORK,
381 /**
382 * 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.
383 */
384 'is_network_activation' => self::OPTION_LEVEL_NETWORK,
385 'license_migration' => self::OPTION_LEVEL_NETWORK,
386
387 // When network activated, then network level.
388 'install_timestamp' => self::OPTION_LEVEL_NETWORK_ACTIVATED,
389 'prev_is_premium' => self::OPTION_LEVEL_NETWORK_ACTIVATED,
390 'require_license_activation' => self::OPTION_LEVEL_NETWORK_ACTIVATED,
391
392 // If not network activated OR delegated, then site level.
393 'activation_timestamp' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
394 'expired_license_notice_shown' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
395 'is_whitelabeled' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
396 'last_license_key' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
397 'last_license_user_id' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
398 'prev_user_id' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
399 'sticky_optin_added' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
400 'uninstall_reason' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
401 'is_pending_activation' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
402 'pending_license_key' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED,
403
404 // Site level options.
405 'is_anonymous' => self::OPTION_LEVEL_SITE,
406 'clone_id' => self::OPTION_LEVEL_SITE,
407 );
408 }
409
410 /**
411 * This method will and should only be executed when is_multisite() is true.
412 *
413 * @author Vova Feldman (@svovaf)
414 * @since 2.0.0
415 *
416 * @param string $key
417 * @param int $option_level Since 2.5.1
418 *
419 * @return bool
420 */
421 private function is_multisite_option( $key, $option_level = self::OPTION_LEVEL_UNDEFINED ) {
422 if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) {
423 self::load_network_options_map();
424 }
425
426 if (
427 self::OPTION_LEVEL_UNDEFINED === $option_level &&
428 isset( self::$_NETWORK_OPTIONS_MAP[ $key ] )
429 ) {
430 $option_level = self::$_NETWORK_OPTIONS_MAP[ $key ];
431 }
432
433 if ( self::OPTION_LEVEL_UNDEFINED === $option_level ) {
434 // Option not found -> use site level storage.
435 return false;
436 }
437
438 if ( self::OPTION_LEVEL_NETWORK === $option_level ) {
439 // Option found and set to always use the network level storage on a multisite.
440 return true;
441 }
442
443 if ( self::OPTION_LEVEL_SITE === $option_level ) {
444 // Option found and set to always use the site level storage on a multisite.
445 return false;
446 }
447
448 if ( ! $this->_is_network_active ) {
449 return false;
450 }
451
452 if ( self::OPTION_LEVEL_NETWORK_ACTIVATED === $option_level ) {
453 // Network activated.
454 return true;
455 }
456
457 if (
458 self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED === $option_level &&
459 ! $this->_is_delegated_connection
460 ) {
461 // Network activated and not delegated.
462 return true;
463 }
464
465 return false;
466 }
467
468 /**
469 * @author Leo Fajardo
470 *
471 * @param string $key
472 * @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).
473 * @param int $option_level Since 2.5.1
474 *
475 * @return bool
476 */
477 private function should_use_network_storage(
478 $key,
479 $network_level_or_blog_id = null,
480 $option_level = self::OPTION_LEVEL_UNDEFINED
481 ) {
482 if ( ! $this->_is_multisite ) {
483 // Not a multisite environment.
484 return false;
485 }
486
487 if ( is_numeric( $network_level_or_blog_id ) ) {
488 // Explicitly asked to use a specified blog storage.
489 return false;
490 }
491
492 if ( is_bool( $network_level_or_blog_id ) ) {
493 // Explicitly specified whether it should use the network or blog level storage.
494 return $network_level_or_blog_id;
495 }
496
497 // Determine which storage to use based on the option.
498 return $this->is_multisite_option( $key, $option_level );
499 }
500
501 /**
502 * @author Vova Feldman (@svovaf)
503 * @since 2.0.0
504 *
505 * @param int $blog_id
506 *
507 * @return \FS_Key_Value_Storage
508 */
509 private function get_site_storage( $blog_id = 0 ) {
510 if ( ! is_numeric( $blog_id ) ||
511 $blog_id == $this->_blog_id ||
512 0 == $blog_id
513 ) {
514 return $this->_storage;
515 }
516
517 return FS_Key_Value_Storage::instance(
518 $this->_module_type . '_data',
519 $this->_storage->get_secondary_id(),
520 $blog_id
521 );
522 }
523
524 #endregion
525
526 #--------------------------------------------------------------------------------
527 #region Magic methods
528 #--------------------------------------------------------------------------------
529
530 function __set( $k, $v ) {
531 if ( $this->should_use_network_storage( $k ) ) {
532 $this->_network_storage->{$k} = $v;
533 } else {
534 $this->_storage->{$k} = $v;
535 }
536 }
537
538 function __isset( $k ) {
539 return $this->should_use_network_storage( $k ) ?
540 isset( $this->_network_storage->{$k} ) :
541 isset( $this->_storage->{$k} );
542 }
543
544 function __unset( $k ) {
545 if ( $this->should_use_network_storage( $k ) ) {
546 unset( $this->_network_storage->{$k} );
547 } else {
548 unset( $this->_storage->{$k} );
549 }
550 }
551
552 function __get( $k ) {
553 return $this->should_use_network_storage( $k ) ?
554 $this->_network_storage->{$k} :
555 $this->_storage->{$k};
556 }
557
558 #endregion
559 }
560