<?php
/**
 *
 * @package XCube
 * @version $Id: XCube_Delegate.class.php,v 1.1.2.5 2007/01/17 08:22:48 minahito Exp $
 * @copyright Copyright 2005, 2006 XOOPS Cube Project <http://xoopscube.org/>
 * @license http://xoopscube.sourceforge.net/bsd_licenses.txt Modified BSD license
 *
 */

/**
 * This class is an expression of reference in delegation mechanism for PHP4.
 */
class XCube_Ref
{
	var $_mObject = null;

	function XCube_Ref(&$obj)
	{
		$this->_mObject =& $obj;
	}

	function &getObject()
	{
		return $this->_mObject;
	}
}

define("XCUBE_DELEGATE_PRIORITY_FIRST", 0);
define("XCUBE_DELEGATE_PRIORITY_NORMAL", 50);
define("XCUBE_DELEGATE_PRIORITY_FINAL", 100);

/**
 * This is simple mechanism for common delegation in XCube.
 * 
 * A delegate can have $callback as connected function, $filepath for lazy
 * loading and $priority as order indicated.
 * 
 * "Priority"
 * Default of this parameter is XCUBE_DELEGATE_PRIORITY_NORMAL. Usually, this
 * parameter isn't specified. Plus, the magic number should be used to specify
 * priority. Use XCUBE_DELEGATE_PRIORITY_FIRST or XCUBE_DELEGATE_PRIORITY_FINAL
 * with Addition and Subtraction. (e.x. XCUBE_DELEGATE_PRIORITY_NORMAL - 1 )
 * 
 * [Notice]
 * This is the candidate as new delegate style, which has foolish name to escape
 * conflict with old XCube_Delegate. After replacing, we'll change all.
 */
class XCube_Delegate
{
	var $_mSignatures = array();
	
	/**
	 * This is Array for callback type data.
	 * 
	 * @var array
	 */
	var $_mCallbacks = array();
	
	/**
	 * @var bool
	 */	
	var $_mHasCheckSignatures = false;
	
	/**
	 * If register() is failed, this flag become true. That problem is raised,
	 * when register() is called before $root come to have the delegate
	 * manager.
	 * 
	 * @var bool
	 */
	var $_mIsLazyRegister = false;
	
	/**
	 * This is register name for lazy registering.
	 */
	var $_mLazyRegisterName = null;

    var $_mUniqueID;
	/**
	 * Constructor. The parameter of the constructor is a variable argument
	 * style to specify the sigunature of this delegate. If the argument is
	 * empty, signature checking doesn't work. Empty arguments are good to
	 * use in many cases. But, that's important to accent a delegate for making
	 * rightly connected functions.
	 * 
	 * [Sample]
	 * $delegate =& new XCube_Delegate("string", "string");
	 * 
	 */
	function XCube_Delegate()
	{
		if (func_num_args() > 0) {
			$this->_setSignatures(func_get_args());
		}
		$this->_mUniqueID = md5(uniqid(rand(), true));
	}
	
	/**
	 * Set signatures for this delegate. By this member function, this function
	 * will come to check arguments with following signatures at call().
	 * 
	 * @access privated
	 * @param $args array
	 */
	function _setSignatures($args)
	{
		$this->_mSignatures = $args;
		for ($i=0 ; $i<count($args) ; $i++) {
			$idx = strpos($this->_mSignatures[$i], " &");
			if ($idx > 0) {
				$this->_mSignatures[$i] = substr($this->_mSignatures[$i], 0, $idx);
			}
		}
		$this->_mHasCheckSignatures = true;
	}
	
	/**
	 * Register this object to delegate manager of root.
	 *
	 * @access public
	 * @param $delegateName string
	 * @return bool
	 */
	function register($delegateName)
	{
		$root =& XCube_Root::getSingleton();
		if ($root->mDelegateManager != null) {
			$this->_mIsLazyRegister = false;
			$this->_mLazyRegisterName = null;
		
			return $root->mDelegateManager->register($delegateName, $this);
		}
		
		$this->_mIsLazyRegister = true;
		$this->_mLazyRegisterName = $delegateName;

		return false;
	}

	/**
	 * Connect any functions to this object. This member function is virtual
	 * overload by sigunatures.
	 * 
	 * add(callback $callback, int priority = XCUBE_DELEGATE_PRIORITY_NORMAL);
	 * add(callback $callback, string filepath = null);
	 * add(callback $callback, int priority =... , string filepath=...);
	 *
	 * @access public
	 * @return void
	 */
	function add($callback, $param2 = null, $param3 = null)
	{
		$priority = XCUBE_DELEGATE_PRIORITY_NORMAL;
		$filepath = null;
		
		if (!is_array($callback) && strstr($callback, '::') !== false) {
			$tmp = explode("::", $callback);
			if (count($tmp) == 2) {
				$callback = array ($tmp[0], $tmp[1]);
			}
		}
		
		if ($param2 !== null && is_int($param2)) {
			$priority = $param2;
			$filepath = ($param3 !== null && is_string($param3)) ? $param3 : null;
		}
		elseif ($param2 !== null && is_string($param2)) {
			$filepath = $param2;
		}
		
		$this->_mCallbacks[$priority][] = array($callback, $filepath);
        ksort($this->_mCallbacks);
	}
	
	/**
	 * Disconnect a function from this object.
	 * @access public
	 * @return void
	 */
	function delete($delcallback)
	{
		foreach (array_keys($this->_mCallbacks) as $priority) {
            foreach (array_keys($this->_mCallbacks[$priority]) as $idx) {
                $callback = $this->_mCallbacks[$priority][$idx][0];
                if (XCube_DelegateUtils::_compareCallback($callback, $delcallback)) {
                    unset($this->_mCallbacks[$priority][$idx]);
                }
                if (count($this->_mCallbacks[$priority])==0) {
                    unset($this->_mCallbacks[$priority]);
                }
            }
        }
    }

	/**
	 * Reset all delegate functions from this object.
	 * @access public
	 * @return void
	 */
	function reset()
	{
	    unset($this->_mCallbacks);
	    $this->_mCallbacks = array();
    }

	/**
	 * Call connected functions.
	 *
	 * @access public
	 */
	function call()
	{
		$args = func_get_args();
		$num = func_num_args();
		
		if ($this->_mIsLazyRegister) {
			$this->register($this->_mLazyRegisterName);
		}
		
		if ($this->_mHasCheckSignatures) {
			if (count($this->_mSignatures) != $num) {
				return false;
			}
		}
		
		$param = array();
		for ($i=0 ; $i<$num ;$i++) {
			if (is_a($args[$i], "XCube_Ref")) {
				$args[$i] =& $args[$i]->getObject();
			}
			
			if ($this->_mHasCheckSignatures) {
				if (!isset($this->_mSignatures[$i])) {
					return false;
				}
				
				switch ($this->_mSignatures[$i]) {
					case "void":
						break;
					
					case "bool":
						if (!empty($args[$i])) {
							$args[$i] = $args[$i] ? true : false;
						}
						break;

					case "int":
						if (!empty($args[$i])) {
							$args[$i] = intval($args[$i]);
						}
						break;
					
					case "float":
						if (!empty($args[$i])) {
							$args[$i] = floatval($args[$i]);
						}
						break;

					case "string":
						if (!empty($args[$i]) && is_string($args[$i])) {
							return false;
						}
						break;
					
					default:
						if (!is_a($args[$i], $this->_mSignatures[$i])) {
							return false;
						}
				}
			}
		
			$param[] = '$args[' . $i . ']';
		}
		
		if (count($param) > 0) {
			$argstr = "(" . join($param, ",") . ");";
		}
		else {
			$argstr = "()";
		}

		foreach ($this->_mCallbacks as $callback_arrays) {
            foreach ($callback_arrays as $callback_array) {
                $callback = $callback_array[0];

               	if ($callback_array[1] != null && file_exists($callback_array[1])) {
               		require_once $callback_array[1];
               	}
               	if (is_callable($callback)) {
               		call_user_func_array($callback, $args);
               	}
            }
		}
	}
	
	/**
	 * Gets a value indicating whether this object has callback functions.
	 * @public
	 * @return bool
	 */    
    function isEmpty()
    {
    	return (count($this->_mCallbacks) == 0);
    }
    
	function getID()
	{
	    return $this->_mUniqueID;
	}
}

/**
 * This is the agent of un-registered delegate objects. Usually, connected
 * functions can't be added to un-registered delegates. When destination
 * delegates are un-registered yet, this manager is keeping those functions
 * and parameters until the destination delegate will be registered.
 * 
 * In other words, this class realizes lazy delegate registering.
 */
class XCube_DelegateManager
{
	var $_mCallbacks = array();
	var $_mCallbackParameters = array();
	
	var $_mDelegates = array();

	function XCube_DelegateManager()
	{
	}
	
	/**
	 * Add $delegate as Delegate to the list of this manager. If some functions
	 * that want to connect to $delegate, have been entrusted yet, this object
	 * calls add() of $delegate with their parameters.
	 * 
	 * Usually this member function isn't used as Cube's API by developers. In
	 * many cases, XCube_Delegate::register() calls this.
	 * 
	 * @access public
	 * 
	 */
	function register($name, &$delegate)
	{
		if (!isset($this->_mDelegates[$name][$delegate->getID()])) {
			$this->_mDelegates[$name][$delegate->getID()] =& $delegate;
			
			if (isset($this->_mCallbacks[$name]) && count($this->_mCallbacks[$name]) > 0) {
				foreach (array_keys($this->_mCallbacks[$name]) as $key) {
					$delegate->add($this->_mCallbacks[$name][$key], $this->_mCallbackParameters[$name][$key][0], $this->_mCallbackParameters[$name][$key][1]);
				}
			}
			
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * Connect any functions to the delegate that have the specified name. If
	 * there aren't any delegates that have the specified name, this manager
	 * entrust parameters to member properties. Then, when the delegate that
	 * have the specified name will be registered, this manager will set these
	 * parameters to the delegate.
	 *
	 * @access public
	 * @param $name register name
	 * 
	 * @see XCube_Delegate::add()
	 */
	function add($name, $callback, $param3 = null, $param4 = null)
	{
		if (isset($this->_mDelegates[$name])) {
		    foreach(array_keys($this->_mDelegates[$name]) as $key) {
			    $this->_mDelegates[$name][$key]->add($callback, $param3, $param4);
			}
		}
		$this->_mCallbacks[$name][] = $callback;
		$this->_mCallbackParameters[$name][] = array('0' => $param3, '1' => $param4);
	}
	
	/**
	 * Disconnect a function from the delegate that have the specified name.
	 *
	 * @access public
	 * @param $name register name
	 * 
	 * @see XCube_Delegate::delete()
	 */
	function delete($name, $delcallback)
	{
		if (isset($this->_mDelegates[$name])) {
		    foreach(array_keys($this->_mDelegates[$name]) as $key) {
    			$this->_mDelegates[$name][$key]->delete($delcallback);
    	    }
    	}
	    if (isset($this->_mCallbacks[$name])) {
	        foreach(array_keys($this->_mCallbacks[$name]) as $key) {
                $callback = $this->_mCallbacks[$name][$key];
                if (XCube_DelegateUtils::_compareCallback($callback, $delcallback)) {
                    unset($this->_mCallbacks[$name][$key]);
                    unset($this->_mCallbackParameters[$name][$key]);
                }
	        }
	    }
	}
	
	/**
	 * Reset all functions off the delegate that have the specified name.
	 *
	 * @access public
	 * 
	 * @see XCube_Delegate::reset()
	 */
	function reset($name)
	{
		if (isset($this->_mDelegates[$name])) {
		    foreach(array_keys($this->_mDelegates[$name]) as $key) {
    			$this->_mDelegates[$name][$key]->reset();
    		}
		}
	    if (isset($this->_mCallbacks[$name])) {
	        unset($this->_mCallbacks[$name]);
	        unset($this->_mCallbackParameters[$name]);
	    }
	}
	
	/**
	 * Gets a value indicating whether the specified delegate has callback functions.
	 * @public
	 * @param string $name delegate's name
	 * @return bool
	 */
	function isEmpty($name)
	{
		if (isset($this->_mDelegates[$name])) {
			return $this->_mDelegates[$name]->isEmpty();
		}
		
		return isset($this->_mCallbacks[$name]) ? (count($this->_mCallbacks[$name]) == 0) : false;
	}
	
	
   /**
    * 
    * @access public
    * @return array of XCube_Delegate
    */
	function getDelegates()
	{
	    return $this->_mDelegates;
	}
}

/**
 * Delegate Utility functions
 *    XCube_DelegateUtils::call("Delegate Name"[, fuction args...]);
 *    XCube_DelegateUtils::raiseEvent("Event Name"[, fuction params...]);
 *    $string = XCube_DelegateUtils::applyStringFilter("Filter Name", $string, [, option params...]);
 */

class XCube_DelegateUtils
{
    /**
     * Calls a Delegate function quickly
     *
     * @access public
     * @param 1st  : Delaget Name
     * @param 2nd and more : Delegate function parameters
     * @return bool
     */
    function call()
    {
        $args = func_get_args();
        $num = func_num_args();
        if ($num > 0) {
            $delegateName = $args[0];
            if ($num > 1) {
                array_shift($args);
            }
        } else {
            return false;
        }
        $root =& XCube_Root::getSingleton();
        if ($root->mDelegateManager != null) {
            $delegates = $root->mDelegateManager->getDelegates();
            if (isset($delegates[$delegateName])) {
                $keys = array_keys($delegates[$delegateName]);
                $delegate =& $delegates[$delegateName][$keys[0]];
            } else {
                $delegate =& new XCube_Delegate;
                $root->mDelegateManager->register($delegateName, $delegate);
            }
        }
        return call_user_func_array(array(&$delegate,'call'),$args);
    }

    /**
     * Alias of call method
     *
     * @access public
     * @param 1st  Delaget Name
     * @param 2nd and more : Delegate function parameters
     * @return bool
     */
    function raiseEvent()
    {
        $args = func_get_args();
        $num = func_num_args();
        if ($num > 0) {
            return call_user_func_array(array('XCube_DelegateUtils','call'),$args);
        }
    }

    /**
     * Calls a delegate string filter function
     *
     * @access public
     * @param 1st  : Delaget Name
     * @param 2nd  : String
     * @param 3rd  and more : Optional function paramaters
     * @return string
     */
    function applyStringFilter()
    {
        $args = func_get_args();
        $num = func_num_args();
        if ($num > 1) {
            $delegateName = $args[0];
            $string = $args[1];
            if (!empty($string) && is_string($string)) {
                return "";
            }
            $args[1] =& new XCube_Ref($string);
            call_user_func_array(array('XCube_DelegateUtils','call'),$args);
            return $string;
        } else {
            return "";
        }
    }
    
    function _compareCallback($callback1, $callback2) {
    /**
     * Comparing two callback (PHP4 cannot compare Object exactly)
     *
     * @access file
     * @param $callback1  : callback
     * @param $callback2  : callback
     * @return bool
     */
        if (!is_array($callback1) && !is_array($callback2) && ($callback1 === $callback2)) {
            return true;
        } elseif (is_array($callback1) && is_array($callback2) && (gettype($callback1[0]) === gettype($callback2[0])) 
                                                               && ($callback1[1] === $callback2[1])) {
            if (!is_object($callback1[0]) && ($callback1[0] === $callback2[0])) {
                return true;
            } elseif (is_object($callback1[0]) && (get_class($callback1[0]) === get_class($callback2[0]))) {
                return true;
            }
        }
        return false;
    }
}
?>