| @@ -14,11 +14,14 @@ | ||
| 14 | 14 | * Class FS_Storage |
| 15 | 15 | * |
| 16 | 16 | * A wrapper class for handling network level and single site level storage. |
| 17 | 17 | * |
| 18 | - * @property bool $is_network_activation | |
| 19 | - * @property int $network_install_blog_id | |
| 20 | - * @property object $sync_cron | |
| 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 | |
| 21 | 24 | */ |
| 22 | 25 | class FS_Storage { |
| 23 | 26 | /** |
| 24 | 27 | * @var FS_Storage[] |
| @@ -71,8 +74,18 @@ | ||
| 71 | 74 | * } |
| 72 | 75 | */ |
| 73 | 76 | private static $_NETWORK_OPTIONS_MAP; |
| 74 | 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 | + | |
| 75 | 88 | /** |
| 76 | 89 | * @author Leo Fajardo (@leorw) |
| 77 | 90 | * |
| 78 | 91 | * @param string $module_type |
| @@ -141,12 +154,19 @@ | ||
| 141 | 154 | * |
| 142 | 155 | * @param string $key |
| 143 | 156 | * @param mixed $value |
| 144 | 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 | |
| 145 | 159 | * @param bool $flush |
| 146 | 160 | */ |
| 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 ) ) { | |
| 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 ) ) { | |
| 149 | 169 | $this->_network_storage->store( $key, $value, $flush ); |
| 150 | 170 | } else { |
| 151 | 171 | $storage = $this->get_site_storage( $network_level_or_blog_id ); |
| 152 | 172 | $storage->store( $key, $value, $flush ); |
| @@ -198,13 +218,19 @@ | ||
| 198 | 218 | * |
| 199 | 219 | * @param string $key |
| 200 | 220 | * @param mixed $default |
| 201 | 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 | |
| 202 | 223 | * |
| 203 | 224 | * @return mixed |
| 204 | 225 | */ |
| 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 ) ) { | |
| 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 ) ) { | |
| 207 | 233 | return $this->_network_storage->get( $key, $default ); |
| 208 | 234 | } else { |
| 209 | 235 | $storage = $this->get_site_storage( $network_level_or_blog_id ); |
| 210 | 236 | |
| @@ -288,21 +314,8 @@ | ||
| 288 | 314 | if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) { |
| 289 | 315 | // Migrate option to the network storage. |
| 290 | 316 | $this->_network_storage->store( $option, $this->_storage->{$option}, false ); |
| 291 | 317 | |
| 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 | 318 | $updated = true; |
| 306 | 319 | } |
| 307 | 320 | } |
| 308 | 321 | |
| @@ -335,64 +348,64 @@ | ||
| 335 | 348 | */ |
| 336 | 349 | private static function load_network_options_map() { |
| 337 | 350 | self::$_NETWORK_OPTIONS_MAP = array( |
| 338 | 351 | // 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, | |
| 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, | |
| 368 | 382 | /** |
| 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. | |
| 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. | |
| 372 | 384 | */ |
| 373 | - 'is_network_activation' => 0, | |
| 385 | + 'is_network_activation' => self::OPTION_LEVEL_NETWORK, | |
| 386 | + 'license_migration' => self::OPTION_LEVEL_NETWORK, | |
| 374 | 387 | |
| 375 | 388 | // When network activated, then network level. |
| 376 | - 'install_timestamp' => 1, | |
| 377 | - 'prev_is_premium' => 1, | |
| 378 | - 'require_license_activation' => 1, | |
| 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, | |
| 379 | 392 | |
| 380 | 393 | // 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, | |
| 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, | |
| 392 | 404 | |
| 393 | 405 | // Site level options. |
| 394 | - 'is_anonymous' => 3, | |
| 406 | + 'is_anonymous' => self::OPTION_LEVEL_SITE, | |
| 407 | + 'clone_id' => self::OPTION_LEVEL_SITE, | |
| 395 | 408 | ); |
| 396 | 409 | } |
| 397 | 410 | |
| 398 | 411 | /** |
| @@ -401,27 +414,35 @@ | ||
| 401 | 414 | * @author Vova Feldman (@svovaf) |
| 402 | 415 | * @since 2.0.0 |
| 403 | 416 | * |
| 404 | 417 | * @param string $key |
| 418 | + * @param int $option_level Since 2.5.1 | |
| 405 | 419 | * |
| 406 | - * @return bool|mixed | |
| 420 | + * @return bool | |
| 407 | 421 | */ |
| 408 | - private function is_multisite_option( $key ) { | |
| 422 | + private function is_multisite_option( $key, $option_level = self::OPTION_LEVEL_UNDEFINED ) { | |
| 409 | 423 | if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { |
| 410 | 424 | self::load_network_options_map(); |
| 411 | 425 | } |
| 412 | 426 | |
| 413 | - if ( ! isset( self::$_NETWORK_OPTIONS_MAP[ $key ] ) ) { | |
| 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 ) { | |
| 414 | 435 | // Option not found -> use site level storage. |
| 415 | 436 | return false; |
| 416 | 437 | } |
| 417 | 438 | |
| 418 | - if ( 0 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { | |
| 439 | + if ( self::OPTION_LEVEL_NETWORK === $option_level ) { | |
| 419 | 440 | // Option found and set to always use the network level storage on a multisite. |
| 420 | 441 | return true; |
| 421 | 442 | } |
| 422 | 443 | |
| 423 | - if ( 3 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { | |
| 444 | + if ( self::OPTION_LEVEL_SITE === $option_level ) { | |
| 424 | 445 | // Option found and set to always use the site level storage on a multisite. |
| 425 | 446 | return false; |
| 426 | 447 | } |
| 427 | 448 | |
| @@ -428,14 +449,17 @@ | ||
| 428 | 449 | if ( ! $this->_is_network_active ) { |
| 429 | 450 | return false; |
| 430 | 451 | } |
| 431 | 452 | |
| 432 | - if ( 1 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { | |
| 453 | + if ( self::OPTION_LEVEL_NETWORK_ACTIVATED === $option_level ) { | |
| 433 | 454 | // Network activated. |
| 434 | 455 | return true; |
| 435 | 456 | } |
| 436 | 457 | |
| 437 | - if ( 2 === self::$_NETWORK_OPTIONS_MAP[ $key ] && ! $this->_is_delegated_connection ) { | |
| 458 | + if ( | |
| 459 | + self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED === $option_level && | |
| 460 | + ! $this->_is_delegated_connection | |
| 461 | + ) { | |
| 438 | 462 | // Network activated and not delegated. |
| 439 | 463 | return true; |
| 440 | 464 | } |
| 441 | 465 | |
| @@ -446,12 +470,17 @@ | ||
| 446 | 470 | * @author Leo Fajardo |
| 447 | 471 | * |
| 448 | 472 | * @param string $key |
| 449 | 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 | |
| 450 | 475 | * |
| 451 | 476 | * @return bool |
| 452 | 477 | */ |
| 453 | - private function should_use_network_storage( $key, $network_level_or_blog_id = null ) { | |
| 478 | + private function should_use_network_storage( | |
| 479 | + $key, | |
| 480 | + $network_level_or_blog_id = null, | |
| 481 | + $option_level = self::OPTION_LEVEL_UNDEFINED | |
| 482 | + ) { | |
| 454 | 483 | if ( ! $this->_is_multisite ) { |
| 455 | 484 | // Not a multisite environment. |
| 456 | 485 | return false; |
| 457 | 486 | } |
| @@ -461,14 +490,14 @@ | ||
| 461 | 490 | return false; |
| 462 | 491 | } |
| 463 | 492 | |
| 464 | 493 | if ( is_bool( $network_level_or_blog_id ) ) { |
| 465 | - // Explicitly specified whether should use the network or blog level storage. | |
| 494 | + // Explicitly specified whether it should use the network or blog level storage. | |
| 466 | 495 | return $network_level_or_blog_id; |
| 467 | 496 | } |
| 468 | 497 | |
| 469 | 498 | // Determine which storage to use based on the option. |
| 470 | - return $this->is_multisite_option( $key ); | |
| 499 | + return $this->is_multisite_option( $key, $option_level ); | |
| 471 | 500 | } |
| 472 | 501 | |
| 473 | 502 | /** |
| 474 | 503 | * @author Vova Feldman (@svovaf) |
| @@ -527,5 +556,5 @@ | ||
| 527 | 556 | $this->_storage->{$k}; |
| 528 | 557 | } |
| 529 | 558 | |
| 530 | 559 | #endregion |
| 531 | - } | |
| 560 | + } | |