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