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