522 lines
16 KiB
PHP
522 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* @package Freemius
|
|
* @copyright Copyright (c) 2015, Freemius, Inc.
|
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
|
|
* @since 1.0.3
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* 3-layer lazy options manager.
|
|
* layer 3: Memory
|
|
* layer 2: Cache (if there's any caching plugin and if WP_FS__DEBUG_SDK is FALSE)
|
|
* layer 1: Database (options table). All options stored as one option record in the DB to reduce number of DB
|
|
* queries.
|
|
*
|
|
* If load() is not explicitly called, starts as empty manager. Same thing about saving the data - you have to
|
|
* explicitly call store().
|
|
*
|
|
* Class Freemius_Option_Manager
|
|
*/
|
|
class FS_Option_Manager {
|
|
/**
|
|
* @var string
|
|
*/
|
|
private $_id;
|
|
/**
|
|
* @var array|object
|
|
*/
|
|
private $_options;
|
|
/**
|
|
* @var FS_Logger
|
|
*/
|
|
private $_logger;
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @var int The ID of the blog that is associated with the current site level options.
|
|
*/
|
|
private $_blog_id = 0;
|
|
|
|
/**
|
|
* @since 2.0.0
|
|
* @var bool
|
|
*/
|
|
private $_is_network_storage;
|
|
|
|
/**
|
|
* @var bool|null
|
|
*/
|
|
private $_autoload;
|
|
|
|
/**
|
|
* @var array[string]FS_Option_Manager {
|
|
* @key string
|
|
* @value FS_Option_Manager
|
|
* }
|
|
*/
|
|
private static $_MANAGERS = array();
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*
|
|
* @param string $id
|
|
* @param bool $load
|
|
* @param bool|int $network_level_or_blog_id Since 2.0.0
|
|
* @param bool|null $autoload
|
|
*/
|
|
private function __construct(
|
|
$id,
|
|
$load = false,
|
|
$network_level_or_blog_id = false,
|
|
$autoload = null
|
|
) {
|
|
$id = strtolower( $id );
|
|
|
|
$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_opt_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
|
|
|
|
$this->_logger->entrance();
|
|
$this->_logger->log( 'id = ' . $id );
|
|
|
|
$this->_id = $id;
|
|
|
|
$this->_autoload = $autoload;
|
|
|
|
if ( is_multisite() ) {
|
|
$this->_is_network_storage = ( true === $network_level_or_blog_id );
|
|
|
|
if ( is_numeric( $network_level_or_blog_id ) ) {
|
|
$this->_blog_id = $network_level_or_blog_id;
|
|
}
|
|
} else {
|
|
$this->_is_network_storage = false;
|
|
}
|
|
|
|
if ( $load ) {
|
|
$this->load();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*
|
|
* @param string $id
|
|
* @param bool $load
|
|
* @param bool|int $network_level_or_blog_id Since 2.0.0
|
|
* @param bool|null $autoload
|
|
*
|
|
* @return \FS_Option_Manager
|
|
*/
|
|
static function get_manager(
|
|
$id,
|
|
$load = false,
|
|
$network_level_or_blog_id = false,
|
|
$autoload = null
|
|
) {
|
|
$key = strtolower( $id );
|
|
|
|
if ( is_multisite() ) {
|
|
if ( true === $network_level_or_blog_id ) {
|
|
$key .= ':ms';
|
|
} else if ( is_numeric( $network_level_or_blog_id ) && $network_level_or_blog_id > 0 ) {
|
|
$key .= ":{$network_level_or_blog_id}";
|
|
} else {
|
|
$network_level_or_blog_id = get_current_blog_id();
|
|
|
|
$key .= ":{$network_level_or_blog_id}";
|
|
}
|
|
}
|
|
|
|
if ( ! isset( self::$_MANAGERS[ $key ] ) ) {
|
|
self::$_MANAGERS[ $key ] = new FS_Option_Manager(
|
|
$id,
|
|
$load,
|
|
$network_level_or_blog_id,
|
|
$autoload
|
|
);
|
|
} // If load required but not yet loaded, load.
|
|
else if ( $load && ! self::$_MANAGERS[ $key ]->is_loaded() ) {
|
|
self::$_MANAGERS[ $key ]->load();
|
|
}
|
|
|
|
return self::$_MANAGERS[ $key ];
|
|
}
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*
|
|
* @param bool $flush
|
|
*/
|
|
function load( $flush = false ) {
|
|
$this->_logger->entrance();
|
|
|
|
$option_name = $this->get_option_manager_name();
|
|
|
|
if ( $flush || ! isset( $this->_options ) ) {
|
|
if ( isset( $this->_options ) ) {
|
|
// Clear prev options.
|
|
$this->clear();
|
|
}
|
|
|
|
$cache_group = $this->get_cache_group();
|
|
|
|
if ( WP_FS__DEBUG_SDK ) {
|
|
|
|
// Don't use cache layer in DEBUG mode.
|
|
$load_options = empty( $this->_options );
|
|
|
|
} else {
|
|
|
|
$this->_options = wp_cache_get(
|
|
$option_name,
|
|
$cache_group
|
|
);
|
|
|
|
$load_options = ( false === $this->_options );
|
|
}
|
|
|
|
$cached = true;
|
|
|
|
if ( $load_options ) {
|
|
if ( $this->_is_network_storage ) {
|
|
$this->_options = get_site_option( $option_name );
|
|
} else if ( $this->_blog_id > 0 ) {
|
|
$this->_options = get_blog_option( $this->_blog_id, $option_name );
|
|
} else {
|
|
$this->_options = get_option( $option_name );
|
|
}
|
|
|
|
if ( is_string( $this->_options ) ) {
|
|
$this->_options = json_decode( $this->_options );
|
|
}
|
|
|
|
// $this->_logger->info('get_option = ' . var_export($this->_options, true));
|
|
|
|
if ( false === $this->_options ) {
|
|
$this->clear();
|
|
}
|
|
|
|
$cached = false;
|
|
}
|
|
|
|
if ( ! WP_FS__DEBUG_SDK && ! $cached ) {
|
|
// Set non encoded cache.
|
|
wp_cache_set( $option_name, $this->_options, $cache_group );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*
|
|
* @return bool
|
|
*/
|
|
function is_loaded() {
|
|
return isset( $this->_options );
|
|
}
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*
|
|
* @return bool
|
|
*/
|
|
function is_empty() {
|
|
return ( $this->is_loaded() && false === $this->_options );
|
|
}
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.6
|
|
*
|
|
* @param bool $flush
|
|
*/
|
|
function clear( $flush = false ) {
|
|
$this->_logger->entrance();
|
|
|
|
$this->_options = array();
|
|
|
|
if ( $flush ) {
|
|
$this->store();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete options manager from DB.
|
|
*
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.9
|
|
*/
|
|
function delete() {
|
|
$option_name = $this->get_option_manager_name();
|
|
|
|
if ( $this->_is_network_storage ) {
|
|
delete_site_option( $option_name );
|
|
} else if ( $this->_blog_id > 0 ) {
|
|
delete_blog_option( $this->_blog_id, $option_name );
|
|
} else {
|
|
delete_option( $option_name );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.6
|
|
*
|
|
* @param string $option
|
|
*
|
|
* @return bool
|
|
*/
|
|
function has_option( $option ) {
|
|
return array_key_exists( $option, $this->_options );
|
|
}
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*
|
|
* @param string $option
|
|
* @param mixed $default
|
|
*
|
|
* @return mixed
|
|
*/
|
|
function get_option( $option, $default = null ) {
|
|
$this->_logger->entrance( 'option = ' . $option );
|
|
|
|
if ( ! $this->is_loaded() ) {
|
|
$this->load();
|
|
}
|
|
|
|
if ( is_array( $this->_options ) ) {
|
|
$value = isset( $this->_options[ $option ] ) ?
|
|
$this->_options[ $option ] :
|
|
$default;
|
|
} else if ( is_object( $this->_options ) ) {
|
|
$value = isset( $this->_options->{$option} ) ?
|
|
$this->_options->{$option} :
|
|
$default;
|
|
} else {
|
|
$value = $default;
|
|
}
|
|
|
|
/**
|
|
* If it's an object, return a clone of the object, otherwise,
|
|
* external changes of the object will actually change the value
|
|
* of the object in the options manager which may lead to an unexpected
|
|
* behaviour and data integrity when a store() call is triggered.
|
|
*
|
|
* Example:
|
|
* $object1 = $options->get_option( 'object1' );
|
|
* $object1->x = 123;
|
|
*
|
|
* $object2 = $options->get_option( 'object2' );
|
|
* $object2->y = 'dummy';
|
|
*
|
|
* $options->set_option( 'object2', $object2, true );
|
|
*
|
|
* If we don't return a clone of option 'object1', setting 'object2'
|
|
* will also store the updated value of 'object1' which is quite not
|
|
* an expected behaviour.
|
|
*
|
|
* @author Vova Feldman
|
|
*/
|
|
return is_object( $value ) ? clone $value : $value;
|
|
}
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*
|
|
* @param string $option
|
|
* @param mixed $value
|
|
* @param bool $flush
|
|
*/
|
|
function set_option( $option, $value, $flush = false ) {
|
|
$this->_logger->entrance( 'option = ' . $option );
|
|
|
|
if ( ! $this->is_loaded() ) {
|
|
$this->clear();
|
|
}
|
|
|
|
/**
|
|
* If it's an object, store a clone of the object, otherwise,
|
|
* external changes of the object will actually change the value
|
|
* of the object in the options manager which may lead to an unexpected
|
|
* behaviour and data integrity when a store() call is triggered.
|
|
*
|
|
* Example:
|
|
* $object1 = new stdClass();
|
|
* $object1->x = 123;
|
|
*
|
|
* $options->set_option( 'object1', $object1 );
|
|
*
|
|
* $object1->x = 456;
|
|
*
|
|
* $options->set_option( 'object2', $object2, true );
|
|
*
|
|
* If we don't set the option as a clone of option 'object1', setting 'object2'
|
|
* will also store the updated value of 'object1' ($object1->x = 456 instead of
|
|
* $object1->x = 123) which is quite not an expected behaviour.
|
|
*
|
|
* @author Vova Feldman
|
|
*/
|
|
$copy = is_object( $value ) ? clone $value : $value;
|
|
|
|
if ( is_array( $this->_options ) ) {
|
|
$this->_options[ $option ] = $copy;
|
|
} else if ( is_object( $this->_options ) ) {
|
|
$this->_options->{$option} = $copy;
|
|
}
|
|
|
|
if ( $flush ) {
|
|
$this->store();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unset option.
|
|
*
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*
|
|
* @param string $option
|
|
* @param bool $flush
|
|
*/
|
|
function unset_option( $option, $flush = false ) {
|
|
$this->_logger->entrance( 'option = ' . $option );
|
|
|
|
if ( is_array( $this->_options ) ) {
|
|
if ( ! isset( $this->_options[ $option ] ) ) {
|
|
return;
|
|
}
|
|
|
|
unset( $this->_options[ $option ] );
|
|
|
|
} else if ( is_object( $this->_options ) ) {
|
|
if ( ! isset( $this->_options->{$option} ) ) {
|
|
return;
|
|
}
|
|
|
|
unset( $this->_options->{$option} );
|
|
}
|
|
|
|
if ( $flush ) {
|
|
$this->store();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dump options to database.
|
|
*
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*/
|
|
function store() {
|
|
$this->_logger->entrance();
|
|
|
|
$option_name = $this->get_option_manager_name();
|
|
|
|
if ( $this->_logger->is_on() ) {
|
|
$this->_logger->info( $option_name . ' = ' . var_export( $this->_options, true ) );
|
|
}
|
|
|
|
// Update DB.
|
|
if ( $this->_is_network_storage ) {
|
|
update_site_option( $option_name, $this->_options );
|
|
} else if ( $this->_blog_id > 0 ) {
|
|
update_blog_option( $this->_blog_id, $option_name, $this->_options );
|
|
} else {
|
|
update_option( $option_name, $this->_options, $this->_autoload );
|
|
}
|
|
|
|
if ( ! WP_FS__DEBUG_SDK ) {
|
|
wp_cache_set( $option_name, $this->_options, $this->get_cache_group() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get options keys.
|
|
*
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 1.0.3
|
|
*
|
|
* @return string[]
|
|
*/
|
|
function get_options_keys() {
|
|
if ( is_array( $this->_options ) ) {
|
|
return array_keys( $this->_options );
|
|
} else if ( is_object( $this->_options ) ) {
|
|
return array_keys( get_object_vars( $this->_options ) );
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
#--------------------------------------------------------------------------------
|
|
#region Migration
|
|
#--------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Migrate options from site level.
|
|
*
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 2.0.0
|
|
*/
|
|
function migrate_to_network() {
|
|
$site_options = FS_Option_Manager::get_manager($this->_id, true, false);
|
|
|
|
$options = is_object( $site_options->_options ) ?
|
|
get_object_vars( $site_options->_options ) :
|
|
$site_options->_options;
|
|
|
|
if ( ! empty( $options ) ) {
|
|
foreach ( $options as $key => $val ) {
|
|
$this->set_option( $key, $val, false );
|
|
}
|
|
|
|
$this->store();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#--------------------------------------------------------------------------------
|
|
#region Helper Methods
|
|
#--------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
private function get_option_manager_name() {
|
|
return $this->_id;
|
|
}
|
|
|
|
/**
|
|
* @author Vova Feldman (@svovaf)
|
|
* @since 2.0.0
|
|
*
|
|
* @return string
|
|
*/
|
|
private function get_cache_group() {
|
|
$group = WP_FS__SLUG;
|
|
|
|
if ( $this->_is_network_storage ) {
|
|
$group .= '_ms';
|
|
} else if ( $this->_blog_id > 0 ) {
|
|
$group .= "_s{$this->_blog_id}";
|
|
}
|
|
|
|
return $group;
|
|
}
|
|
|
|
#endregion
|
|
}
|