Location: PHPKode > projects > Mocovie web framework > mocovi/library/autoload/Control.php
<?php
/**
 *  Copyright (C) 2010  Kai Dorschner
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Kai Dorschner <the-hide@address.com>
 * @copyright Copyright 2010, Kai Dorschner
 * @license http://www.gnu.org/licenses/gpl.html GPLv3
 * @package mocovi
 */

/**
 * Including mocovi exception.
 */
class_exists('MvcException')	or require $GLOBALS['library'].'autoload/MvcException.php';

/**
 * Represents the control-master class.
 *
 * It is the abstract control class which serves methodes to handle data.
 * A control is a complete (little) program.
 *
 * The execution order of the program methods is:
 *
 *	1. {@link load(DomNode $parent)}
 *		1.1. {@link _beforeLoad()}
 *		1.2. {@link _beforeCreateNode()}
 *			1.2.1. {@link createNode()}
 *		1.3. {@link _beforeLoadChilds()}
 *			1.3.1. {@link loadChilds()}
 *				1.3.1.1. {@link _beforeLoadChild()}
 *	2. {@link run()}
 *		2.1. {@link _beforeRun()}
 *		2.2. {@link showtime()}
 *		2.3. {@link _beforeRunChilds()}
 *		2.4. {@link runChilds()}
 *		2.5. {@link program()}
 *
 * To hook in and modify the default behaviour please use the underscore methods like {@link _beforeLoad()}.
 * Their return value (boolean) tells whether the procedure should continue in normal order or not.
 *
 * @abstract
 * @package mocovi
 */
abstract class Control
{
	/**
	 * Reference of the Document Object Model set in the constructor.
	 *
	 * With this reference you are able to create, move, update or delete
	 * XML-elements of the page-model.
	 *
	 * @var DomDocument $dom Contains a DOM reference of the destination DOM
	 * @see function __construct()
	 * @access protected
	 */
	protected $dom;

	/**
	 * Represents the own node as a part of the {@link $dom}.
	 *
	 * @var DomNode $node Contains destination node
	 * @see $dom
	 * @access protected
	 */
	protected $node;

	protected $xpath;

	/**
	 * Contains the node of the parent control.
	 *
	 * @var DomNode $parentNode Contains the destination parent node
	 * @access protected
	 */
	protected $parentNode;

	/**
	 * Contains the source node from model.
	 *
	 * @var DomNode $sourceNode Contains the source node
	 * @access protected
	 */
	protected $sourceNode;

	protected $sourceXpath;
	/**
	 * This array contains all modifiers of this class.
	 *
	 * Options modify the control. You can use it as a switch.
	 * Nothing should be inserted HERE by authors. {@link $defaultOptions}
	 * "Options" is an associative array which containes
	 * [optionname] => (string)[option]
	 *
	 * @var Array Contains control options
	 * @see $defaultOptions
	 * @see __construct()
	 * @see getOption()
	 * @access protected
	 */
	protected $options = array
		( 'showFromDate'	=> '' // used by {@link showtime()}
		, 'showToDate'		=> '' // used by {@link showtime()}
		);

	/**
	 * Default options for the {@see program()} defined by the author.
	 *
	 * An associative array containing all default internal options.
	 * Authors should use THIS to define default values.
	 * These entries will been overwritten from the values of the array {@link $options}
	 *
	 * @var Array Contains the default control options for inheriting classes
	 * @access protected
	 */
	protected $defaultOptions = array(); // Will be overwritten in child controls.

	/**
	 * Contains all subcontrols of this element.
	 *
	 * Every element in this array is type of {@see Control}.
	 *
	 * @var Array Contains all sub controls
	 * @access protected
	 */
	protected $controls = array();


	private $insertMethods = array
		( 'insertBefore'
		, 'appendChild'
		);

	/**
	 * Registers the options (modifiers) for each control.
	 *
	 * You as the author have the possibility to set default options in your
	 * control. Therefore I had to fill these options "key by key" with
	 * a foreach() loop. For {@link $defaultOptions} has got the higher
	 * (user-level) priority it replaces all properties of {@link $options}.
	 *
	 * @param Array $options Contains the options
	 * @see $options
	 * @return void
	 * @access public
	 */
	public function __construct(DomNode $sourceNode)
	{
		$this->options = array_merge
			( $this->defaultOptions
			, $this->extractOptions($this->sourceNode = $sourceNode)
			);
	}

	/**
	 * Loads destination control structure including all children.
	 *
	 * @final
	 * @return Control $this
	 * @param DomNode $parent Parent node.
	 */
	final public function load(DomNode $parent)
	{
		$this->dom			= ($parent instanceof DomDocument ? $parent : $parent->ownerDocument);
		$this->parentNode	= $parent;
		if($this->_beforeLoad($parent))
		{
			if($this->_beforeCreateNode($parent))
				$this->createNode();
			if($this->_beforeLoadChilds($parent))
				$this->loadChilds();
		}
		return $this;
	}

	/**
	 * Loads all children when available.
	 *
	 * @final
	 * @return Control $this
	 */
	final protected function loadChilds()
	{
		if($this->sourceNode->hasChildNodes())
			foreach($this->sourceQuery('mocovi:*|text()') as $child)
				if(in_array($child->nodeType, array(XML_ELEMENT_NODE, XML_TEXT_NODE)))
				{
					$childControl = $this->controls[] = ControlFactory::create($child);
					if($this->_beforeLoadChild($child))
						$childControl->load($this->node);
				}
		return $this;
	}

	/**
	 * This method is called from outside to execute this control.
	 *
	 * Above all, it executes the author's function {@see program()} which
	 * customizes this control.
	 *
	 * @final
	 * @return DomNode Contains the node of this control
	 * @param DomDocument $dom Contains the destination DOM as reference.
	 * @see $dom
	 * @see $node
	 * @see showtime()
	 * @see createNode()
	 * @see runChilds()
	 * @see program()
	 * @access public
	 */
	final public function run()
	{
		if($this->_beforeRun() && $this->showtime())
		{
			try
			{
				if($this->_beforeRunChilds())
					$this->runChilds(); // render childs
				$this->program(); // render self node
			}
			catch(Exception $e)
			{
				$message = '';
				if(version_compare(PHP_VERSION, '5.3', '>='))
				{
					do
						$message .= (strlen($message) > 0 ? ' ---> ' : '').get_class($e).': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine();
					while($e = $e->getPrevious()); // Available since PHP5.3
				}
				else
					$message .= (strlen($message) > 0 ? ' ---> ' : '').get_class($e).': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine();
				$this->error($message);
			}
			return $this->node;
		}
	}

	/**
	 * The {@see run()} method is called in every single child of this control.
	 *
	 * By default the {@link run()} will also been executed in the sub classes,
	 * but you can overwrite the {@link run()} method for each control to disable
	 * this feature. This is reasonable when you want to create elements
	 * where no childelements should be contained in.
	 *
	 * @return Control $this
	 * @see $controls
	 * @see $node
	 * @access protected
	 */
	final protected function runChilds()
	{
		foreach($this->controls as $child)
			if($this->_beforeRunChild($child))
				$child->run();
		return $this;
	}

	/**
	 * Adds a subcontrol into this control.
	 *
	 * @param object Contains the control which is going to be added
	 * @return Control This.
	 * @see $control
	 * @access public
	 * @deprecated
	 */
	// public function addControl(Control $control)
	// {
		// $this->controls[] = $control;
		// return $this;
	// }

	/**
	 * Adds a subcontrol into this control.
	 *
	 * @access public
	 * @return Control new Control (not runned!)
	 * @param String $name
	 * @see $control
	 * @deprecated
	 */
	public function appendControl($name, Array $options)
	{
		$this->controls[] = $control = ControlFactory::createVirtual($name, $options);
		return $control->load($this->node);
	}

	/**
	 * Returns the class name of this control.
	 *
	 * @return string The classname
	 * @access public
	 */
	public function getControlName()
	{
		return get_class($this);
	}

	/**
	 * Returns the name of the precending model element.
	 *
	 * This is realized by cutting off the suffix of the control's name
	 * which was appended before.
	 *
	 * @return string The precending element name.
	 * @access public
	 * @see getControlName()
	 */
	public function getName()
	{
		return substr
			( $controlName = $this->getControlName()
			, 0
			, strlen($controlName) - strlen(ControlFactory::$controlannex)
			);
	}

	/**
	 * Clones this control (deep).
	 *
	 * @return Control Cloned control.
	 * @access public
	 */
	public function getClone()
	{
		return ControlFactory::create($this->sourceNode->cloneNode(true));
	}

	/**
	 * jQuery-like find method to find children in any recursion depth.
	 *
	 * @return array Control set.
	 * @access public
	 */
	public function find($search = null)
	{
		$subControls = array();
		foreach($this->controls as $subControl)
		{
			if(is_null($search))
				$subControls[] = $subControl;
			else
			{
				foreach(preg_split('/\s*,\s*/', $search) as $class)
				{
					$class .= ControlFactory::$controlannex;
					if($subControl instanceof $class)
						$subControls[] = $subControl;
				}
				// if(in_array($subControl->getName(), preg_split('/\s*,\s*/', $search)))
					// $subControls[] = $subControl;
			}
			$subControls = array_merge($subControls, $subControl->find($search)); // Recursion
		}
		return $subControls;
	}

	/**
	 * Returns the value of a specified option (attribute in XML).
	 *
	 * @return string The option's value
	 * @access public
	 * @param string $name The name of the option
	 */
	public function getOption($name)
	{
		if(isset($this->options[$name])) return $this->options[$name];
		if(isset($this->defaultOptions[$name])) return $this->defaultOptions[$name];
		return null;
	}

	/**
	 * Returns all (user defined and default) options of this control as an associative array.
	 *
	 * @return array All options of this control.
	 * @access public
	 */
	public function getOptions()
	{
		return array_merge($this->defaultOptions, $this->options); // At first the defaultOptions and then override with the options.
	}

	/**
	 * Returns all default options.
	 *
	 * @return array All options of this control.
	 * @access public
	 */
	public function getDefaultOptions()
	{
		return $this->defaultOptions;
	}

	/**
	 * Adds text to the own node.
	 *
	 * @return Control This.
	 * @param string $text Contains the text for this node
	 * @see $node
	 * @access public
	 */
	public function addText($text)
	{
		$this->node->appendChild
			( $this->dom->createTextNode($text)
			);
		return $this;
	}

	/**
	 * Checks whether this control has children controls.
	 *
	 * @return bool
	 * @access public
	 */
	public function hasChilds()
	{
		return (count($this->controls) > 0);
	}

	/**
	 * Replaces this control node with a new DomNode.
	 *
	 * @return bool
	 * @access public
	 * @param DomNode $newNode
	 */
	public function replaceNode(DomNode $newNode)
	{
		if($this->node instanceof DomNode)
			$this->node->parentNode->replaceChild
				( $newNode
				, $this->node
				);
		elseif(empty($this->node) && $this->parentNode instanceof DomElement)
			$this->parentNode->appendChild($newNode);
		else
			throw new MvcException('Couldn\'t replace current node. Neither the current node nor the parentNode are instanceof DomNode/DomElement');
		$this->node = $newNode;
		return $this;
	}

	public function renameNode($nodeName)
	{
		$newNode = $this->node->ownerDocument->createElement($nodeName);
		if($this->node->attributes->length > 0)
			foreach($this->node->attributes as $attribute)
				$newNode->setAttribute($attribute->name, $attribute->value);
		while($this->node->firstChild)
			$newNode->appendChild($this->node->firstChild);
		$this->replaceNode($newNode);
		return $this;
	}

	public function sourceQuery($query)
	{
		if(is_null($this->xpath))
		{
			$this->sourceXpath = new DomXpath($this->sourceNode->ownerDocument);
			$this->sourceXpath->registerNameSpace('mocovi', Mocovi::$namespace);
		}
		return $this->sourceXpath->query($query, $this->sourceNode);
	}

	public function query($query)
	{
		if(is_null($this->xpath))
		{
			$this->xpath = new DomXpath($this->dom);
			$this->xpath->registerNameSpace('mocovi', Mocovi::$namespace);
		}
		return $this->xpath->query($query, $this->node);
	}

	public function error($message)
	{
		$this->replaceNode
		(
			ControlFactory::createError
				( $this->getName()
				, $message
				)
				->load($this->parentNode)
				->run()
		);
	}

	/**
	 * This is the authors individual function.
	 *
	 * With this function you have the possibility to create everything
	 * you can figure out.
	 * Inherited from this class and use the {@link program()} method, which is
	 * automatically called by {@link run()} when it's executed.
	 *
	 * Feel free - the horizon is widely opened.
	 *
	 * @return void
	 * @see run()
	 * @access protected
	 */
	protected function program()
	{}

	/**
	 * Tells if this program is in time and will be executed.
	 *
	 * If the current time is in the range set in the model XML, the program
	 * will be executed.
	 *
	 * @return bool Is it "showtime" or not?
	 * @see $options
	 * @see convertToUnixTime()
	 * @access protected
	 */
	protected function showtime()
	{
		if(!$this->node)
			throw new Exception('You have to load control "'.$this->getName().'" first'.(isset($this->parentNode->nodeName) ? ' in '.$this->parentNode->nodeName : ''));
		$from	= $this->convertToUnixTime($this->options['showFromDate']);
		$to		= $this->convertToUnixTime
			( str_replace
				( '*'
				, date('r', $from)
				, $this->options['showToDate']
				)
			);
		if($from <= ($now = time()) && $now <= $to)
			return true;
		return false;
	}

	/**
	 * Converts a datestring in unix timestamp.
	 *
	 * http://www.gnu.org/software/tar/manual/html_chapter/Date-input-formats.html#SEC115
	 *
	 * @return integer Returns either the converted time or the current time
	 * @param string $userdate Contains the the user time format
	 * @see splitDate()
	 * @access protected
	 */
	protected function convertToUnixTime($userdate)
	{
		if(empty($userdate))
			return time(); //now
		return strtotime($userdate);
	}

	/**
	 * Creates the own node and returns it.
	 *
	 * @return Control This.
	 * @see nodeCreated()
	 * @see $node
	 * @see $dom
	 * @access protected
	 */
	final protected function createNode($insertMethod = 'appendChild')
	{
		if(!in_array($insertMethod, $this->insertMethods))
			$insertMethod = 'appendChild';
		$this
			->parentNode
			->$insertMethod
			( $this->node = $this->dom->createElement
				( $this->getName()
				)
			);
		return $this;
	}

	protected function deleteNode()
	{
		$this->replaceNode(new DomText(''));
		return $this;
	}

	/**
	 * Returns whether the own node already has been created.
	 *
	 * @access public
	 * @return bool Am I created or not?
	 * @see $node
	 */
	final public function nodeCreated()
	{
		return ($this->node instanceof DomElement);
	}

	/**
	 * Applies an attribute/option from the model element to the output XML.
	 *
	 * @return Control This.
	 * @see getOption()
	 * @access protected
	 */
	protected function adoptAttribute($option)
	{
		if($value = $this->getOption($option))
			$this->setAttribute
				( $option
				, $value
				);
		return $this;
	}

	protected function setAttribute($name, $value)
	{
		$this->node->setAttribute
			( $name
			, $value
			);
		return $this;
	}

	/**
	 * Applies multiple attributes/options from the model element to the output XML.
	 *
	 * @return Control This.
	 * @see adoptAttribute()
	 * @access protected
	 */
	protected function adoptAttributes(/*args*/)
	{
		foreach(func_get_args() as $arg)
			$this->adoptAttribute($arg);
		return $this;
	}

	/**
	 * Applies all attributes/options from the model element to the output XML.
	 *
	 * @return Control This.
	 * @see adoptAttribute()
	 * @access protected
	 */
	protected function adoptAllAttributes()
	{
		foreach($this->getOptions() as $name => $option)
			if(!in_array($name, array('showFromDate', 'showToDate')))
				$this->adoptAttribute($name);
		return $this;
	}

	private function extractOptions(DomNode $node)
	{
		if(isset($node->attributes))
			foreach($node->attributes as $child)
				$this->options[$child->name] = $child->value;
		return $this->options;
	}

	protected function _beforeLoad(DomNode $parent)
	{
		return true;
	}

	protected function _beforeLoadChilds(DomNode $parent)
	{
		return true;
	}

	protected function _beforeLoadChild(DomNode $child)
	{
		return true;
	}


	protected function _beforeCreateNode(DomNode $parent)
	{
		return true;
	}

	protected function _beforeRun()
	{
		return true;
	}

	protected function _beforeRunChild(Control $child)
	{
		return true;
	}

	protected function _beforeRunChilds()
	{
		return true;
	}
}
Return current item: Mocovie web framework