477 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			477 lines
		
	
	
		
			14 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;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 2-layer lazy options manager.
 | 
						|
     *      layer 2: Memory
 | 
						|
     *      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();
 | 
						|
 | 
						|
            if ( ! $flush && isset( $this->_options ) ) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            if ( isset( $this->_options ) ) {
 | 
						|
                // Clear prev options.
 | 
						|
                $this->clear();
 | 
						|
            }
 | 
						|
 | 
						|
            $option_name = $this->get_option_manager_name();
 | 
						|
 | 
						|
            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();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * @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
 | 
						|
         * @param bool   $flush
 | 
						|
         *
 | 
						|
         * @return bool
 | 
						|
         */
 | 
						|
        function has_option( $option, $flush = false ) {
 | 
						|
            if ( ! $this->is_loaded() || $flush ) {
 | 
						|
                $this->load( $flush );
 | 
						|
            }
 | 
						|
 | 
						|
            return array_key_exists( $option, $this->_options );
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * @author Vova Feldman (@svovaf)
 | 
						|
         * @since  1.0.3
 | 
						|
         *
 | 
						|
         * @param string $option
 | 
						|
         * @param mixed  $default
 | 
						|
         * @param bool   $flush
 | 
						|
         *
 | 
						|
         * @return mixed
 | 
						|
         */
 | 
						|
        function get_option( $option, $default = null, $flush = false ) {
 | 
						|
            $this->_logger->entrance( 'option = ' . $option );
 | 
						|
 | 
						|
            if ( ! $this->is_loaded() || $flush ) {
 | 
						|
                $this->load( $flush );
 | 
						|
            }
 | 
						|
 | 
						|
            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 option 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 );
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * 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;
 | 
						|
        }
 | 
						|
 | 
						|
        #endregion
 | 
						|
    }
 |