<?php
/**
 * base patForms element class with all needed base functionality that each element
 * should have.
 *
 * $Id: Element.php 1347 2009-12-03 21:06:36Z francois $
 *
 * @package    patForms
 * @subpackage patForms_Element
 * @access     protected
 * @author     Sebastian Mordziol <argh@php-tools.net>
 * @author     gERD Schaufelberger <gerd@php-tools.net>
 * @author     Stephan Schmidt <schst@php-tools.net>
 */

/**
 * error definition: the attribute that was set is not supported by this element (it is
 * not listed in the attributeDefinition property set in the element class).
 * @see        patForms_Element::attributeDefinition
 */
define( "PATFORMS_ELEMENT_NOTICE_ATTRIBUTE_NOT_SUPPORTED", 1101 );

/**
 * error definition: the setAttributes() method expects an array,
 * but given value was not.
 * @see        patForms_Element::setAttributes()
 */
define( "PATFORMS_ELEMENT_ERROR_ARRAY_EXPECTED", 1102 );

/**
 * error definition: the given attribute could not be set
 */
define( "PATFORMS_ELEMENT_ERROR_ADDING_ATTRIBUTE_FAILED", 1103 );

/**
 * error definition: the element method to serialize the element in the given mode is
 * not implemented.
 * @see        patForms_Element::serialize()
 */
define( "PATFORMS_ELEMENT_ERROR_METHOD_FOR_MODE_NOT_AVAILABLE", 1104 );

/**
 * error definition: the element returned an error
 */
define( "PATFORMS_ELEMENT_ERROR_ERROR_RETURNED", 1105 );

/**
 * error definition: the utility class {@link patForms_FormatChecker} could not be found, this is
 * needed for the format validation of certain variable types.
 * @see        patForms_FormatChecker
 * @see        patForms_Element::validateFormat()
 */
define( "PATFORMS_ELEMENT_ERROR_FORMAT_CHECKER_NOT_FOUND", 1106 );

/**
 * error definition: the modifier that was set for the element is not an array.
 * @see        patForms_Element::_applyModifiers()
 */
define( "PATFORMS_ELEMENT_ERROR_MODIFIER_NOT_AN_ARRAY", 1107 );

/**
 * error definition: the method for the given modifier does not exist
 * @see        patForms_Element::_applyModifiers()
 */
define( "PATFORMS_ELEMENT_ERROR_METHOD_FOR_MODIFIER_NOT_FOUND", 1108 );

/**
 * error definition: the modifier returned an error, modifications could not be made.
 * @see        patForms_Element::_applyModifiers()
 */
define( "PATFORMS_ELEMENT_ERROR_MODIFIER_RETURNED_ERROR", 1109 );

/**
 * error definition: the given attribute is required for the specified output format.
 * @see        patForms_Element::getAttributesFor()
 */
define( "PATFORMS_ELEMENT_ERROR_ATTRIBUTE_REQUIRED", 1110 );

/**
 * error definition: given modifier could not be applied to specified attribute
 * @see        patForms_Element::getAttributesFor()
 */
define( "PATFORMS_ELEMENT_ERROR_UNABLE_TO_APPLY_MODIFIER_TO_ATTRIBUTE", 1111 );

/**
 * error definition: the given attribute is not available for output in the specified
 * output format.
 * @see        patForms_Element::getAttributesFor()
 */
define( "PATFORMS_ELEMENT_ERROR_ATTRIBUTE_NOT_AVAILABLE_FOR_OUTPUT", 1112 );

/**
 * error definition: format of the attribute could not be verified
 * @see        patForms_Element::getAttributesFor()
 */
define( "PATFORMS_ELEMENT_ERROR_CAN_NOT_VERIFY_FORMAT", 1113 );

/**
 * error definition: the attribute collection of the element could not be validated.
 * @see        patForms_Element::toHtml()
 */
define( "PATFORMS_ELEMENT_ERROR_CAN_NOT_VALIDATE_ATTRIBUTE_COLLECTION", 1114 );

/**
 * error definition: validator undefined
 */
define( "PATFORMS_ELEMENT_ERROR_VALIDATOR_ERROR_UNDEFINED", 1115 );

/**
 * error definition: undefined locale for errors output
 */
define( "PATFORMS_ELEMENT_ERROR_VALIDATOR_ERROR_LOCALE_UNDEFINED", 1116 );

/**
 * error definition: the html source for the element could not be generated.
 */
define( "PATFORMS_ELEMENT_ERROR_NO_HTML_CONTENT", 1221 );

/**
 * error definition: not a valid renderer
 */
define( 'PATFORMS_ELEMENT_ERROR_INVALID_RENDERER', 1222 );

/**
 * error definition: this element does not support the use of a renderer
 */
define( 'PATFORMS_ELEMENT_RENDERER_NOT_SUPPORTED', 1223 );

/**
 * filter is located between patForms and browser
 */
define( 'PATFORMS_FILTER_TYPE_HTTP', 1 );

/**
 * filter is located between patForms and the PHP script
 */
define( 'PATFORMS_FILTER_TYPE_PHP', 2 );

/**
 * base patForms element class with all needed base functionality that each element
 * should have. Extend this class to create your own elements.
 *
 * $Id: Element.php 1347 2009-12-03 21:06:36Z francois $
 *
 * @abstract
 * @package    patForms
 * @subpackage patForms_Element
 * @access     protected
 * @version    0.1
 * @author     Sebastian Mordziol <argh@php-tools.net>
 * @author     gERD Schaufelberger <gerd@php-tools.net>
 * @author     Stephan Schmidt <schst@php-tools.net>
 * @license    LGPL, see license.txt for details
 * @link       http://www.php-tools.net
 */
class patForms_Element
{
   /**
	* the type of the element, set this in your element class!
	* @access     protected
	*/
	var $elementType	=	false;

   /**
	* javascript that will be displayed only once
	*
	* @access     private
	* @var        array
	*/
	var $globalJavascript	=	array();

   /**
	* javascript that will be displayed once per instance
	*
	* @access     private
	* @var        array
	*/
	var $instanceJavascript	=	array();

   /**
	* the value of the element
	* @access     protected
	*/
	var $value		=	false;

   /**
	* filters that have been applied
	* @access     private
	*/
	var $filters	=	array();

   /**
	* observers that have been attached
	*
	* @access     private
	* @var        array
	*/
	var $observers	=	array();

   /**
	* The elementName for the serialized version of the element
	*
	* This is needed for the toXML() method and also by the patForms
	* error management. If it is not set, the element name will be
	* created by extracting everything after the last underscore in
	* the classname.
	*
	* @access     protected
	* @see        toXML()
	*/
	var $elementName	=	null;

   /**
	* the attribute collection of the element
	* @access     private
	* @see        setAttribute()
	* @see        setAttributes()
	* @see        getAttribute()
	* @see        getAttributes()
	*/
	var $attributes	=	array();

   /**
	* the configuration for the attributes supported by the element. Overwrite this
	* in your element class.
	*
	* @abstract
	*/
	var $attributeDefinition	=	array();

   /**
	* Stores the attribute defaults for the element, that will be used
	* if the given attributes have not been set by the user.
	*
	* @abstract
	* @access     private
	* @see        getAttributeDefaults()
	*/
	var $attributeDefaults	=	array();

   /**
	* stores the mode for the element. It defaults to 'default', and is only overwritten if
	* set specifically.
	*
	* @access     protected
	* @see        setMode()
	*/
	var $mode	=	"default";

   /**
	* stores the format for the element. It defaults to 'html', and is only overwritten if
	* set specifically.
	*
	* @access     protected
	* @see        setFormat()
	*/
	var $format	=	"html";

   /**
	* stores the locale to use when adding validation errors. The specified locale has
	* to be set in the validationErrorCodes element class property, otherwise the default
	* 'C' (as in the programming language C => english) will be used.
	*
	* @access     private
	* @var        string	$locale
	* @see        setLocale()
	*/
	var	$locale   =   "C";

   /**
	* stores the flag telling the element whether it has been submitted - this is used by the
	* getValue() method to determine where to get the element's value from.
	* @access     protected
	* @see        getValue()
	*/
	var $submitted	=	false;

   /**
	* stores the flag whether the element is valid
	* @access     protected
	*/
	var $valid	=	true;

   /**
	* stores any validation errors that can occurr during the element's validation process.
	*
	* @access     private
	* @var        array	$validationErrors
	*/
	var	$validationErrors  =   array();

   /**
	* define error codes an messages for each form element
	*
	* @access     protected
	* @var        array	$validatorErrorCodes
	*/
	var	$validatorErrorCodes  =   array();

   /**
	* defines the starting character for the modifier placeholders that can be inserted
	* in the attributes listed as having modifier support.
	*
	* @access     private
	* @var        string	$modifierStart
	*/
	var $modifierStart	=	"[";

   /**
	* defines the starting character for the modifier placeholders that can be inserted
	* in the attributes listed as having modifier support.
	*
	* @access     private
	* @var        string	$modifierStart
	*/
	var $modifierEnd	=	"]";

   /**
	* XML entities
	*
	* @access     protected
	* @see        toXML()
	*/
	var $xmlEntities	=	array(
									"<"	=>	"&lt;",
									">"	=>	"&gt;",
									"&"	=>	"&amp;",
									"'"	=>	"&apos;",
									'"'	=>	"&quot;"
								);
   /**
	* shortcur to the session variables
	* If "false", no session will be used, otherwise it stores the session variables for this element
	*
	* @access     private
	* @var        mixed	$sessionVar
	*/
	var	$sessionVar = false;

   /**
	* custom validation rules
	*
	* @access     private
	* @var        array
	*/
	var $_rules			=	array();

   /**
	* next error offset for rules
	* @access     private
	* @var        integer
	*/
	var $nextErrorOffset	=	1000;

   /**
	* stores whether the element uses a renderer to serialize its content
	* @access     private
	* @var        bool
	*/
	var $usesRenderer		=	false;

   /**
	* Stores the renderer object that can be set via the setRenderer method
	* @access     private
	* @var        object
	*/
	var $renderer			=	false;

   /**
	* Stores all element options
	* @access     private
	*/
	var $options	=	array();

   /**
	* constructor - extend this in your class if you need to do specific operations
	* on startup. In that case, however, don't forget to call this constructor anyway
	* so that the thing happening here don't get lost.
	*
	* That's easy to do... just add the following line in your constructor:
	* parent::patForms_Element();
	*
	* @access     public
	* @param      mixed	$mode	Optional: the output format, e.g. 'html'
	*/
	function __construct( $format = false )
	{
		if ( $format !== false )
		{
			$this->format	=	$format;
		}

		$this->loadAttributeDefaults();
	}

	/**
	 *	patForms_Element	constructor for php4
	 *
	 *	@access	private
	 *	@param	integer	$id
	 *	@return boolean $result	true on success
	 *	@see	__construct
	 */
	function patForms_Element( $format = false )
	{
		$this->__construct( $format );
	}

   /**
	* Add any initialization routines for your element in your element class,
	* for everythig your element needs to do after it has been instantiated and
	* the attribute collection has been created.
	*
	* @abstract
	* @access     private
	* @return     mixed	$success	True on success, a patError object otherwise
	*/
	function _init()
	{
		// your code here
		return true;
	}

   /**
	* sets the format of the element - this defines which method will be called in your
	* element class, along with the {@link mode} property.
	*
	* @access     public
	* @param      string	$format	The name of the format you have implemented in your element(s). Default is 'html'
	* @see        setFormat()
	* @see        format
	* @see        serialize()
	*/
	function setFormat( $format )
	{
		$this->format	=	strtolower( $format );
	}

   /**
	* sets the mode of the element that defines which methods will be called in your
	* element class, along with the {@link format} property.
	*
	* @access     public
	* @param      string	$mode	The mode to set the element to: default|readonly or any other mode you have implemented in your element class(es). Default is 'default'.
	* @see        setFormat()
	* @see        mode
	* @see        serialize()
	*/
	function setMode( $mode )
	{
		$this->mode	=	strtolower( $mode );
	}

   /**
	* sets the locale (language) to use for the validation error messages of the form.
	*
	* @access     public
	* @param      string	$lang
	* @return     bool	$result	True on success
	* @see        $locale
	*/
	function setLocale( $lang )
	{
		$this->locale = $lang;

		// check, whether this is a custom locale
		if (patForms::isCustomLocale($lang)) {
			$errorMessages = patForms::getCustomLocale($lang, 'Element::' . $this->elementName);
			if (is_array($errorMessages)) {
				$this->validatorErrorCodes[$lang] = $errorMessages;
			}
		}

		return  true;
	}

   /**
	* sets the value of the element, which will be used to fill the element with. If none is
	* set and the element needs a value, it will load it using the {@link resolveValue()} method.
	*
	* This will override user input.
	*
	* @access     public
	* @param      mixed	$value	The value to set
	* @see        $value
	* @see        resolveValue()
	* @see        getValue()
	*/
	function setValue( $value )
	{
		$value			=	$this->_applyFilters( $value, 'in', PATFORMS_FILTER_TYPE_PHP );
		$this->value	=	$value;
	}

   /**
	* sets the default value of the element, which will be used to fill the element with.
	*
	* @access     public
	* @param      mixed	$value	The value to set
	* @see        $value
	* @see        resolveValue()
	* @see        setValue()
	* @see        getValue()
	*/
	function setDefaultValue( $value )
	{
		$this->setAttribute('default', $value);
	}

   /**
	* sets the current submitted state of the element. Set this to true if you want the element
	* to pick up its submitted data.
	*
	* @access     public
	* @param      bool	$state	True if it has been submitted, false otherwise (default).
	* @see        getSubmitted()
	* @see        $submitted
	*/
	function setSubmitted( $state )
	{
		$this->submitted	=	$state;
	}

   /**
	* sets the internal ID of the element - this is only used by the {@link patForms} class to
	* give each element a unique ID that will be added as ID attribute to each element if the
	* id attribute has not been defined.
	*
	* @access     public
	* @param      string	$id	The id to set for the element
	* @see        getId()
	*/
	function setId( $id )
	{
		$this->attributes['id']	=	$id;
	}

   /**
	* gets the internal ID of the element
	*
	* @access     public
	* @return     string	$id	The id to set for the element
	* @see        setId()
	*/
	function getId()
	{
		return $this->getAttribute( 'id' );
	}

   /**
	* checks whether a given attribute is supported by this element.
	*
	* @access     public
	* @param      string	$attributeName	The name of the attribute to check
	* @return     bool	$hasAttribute	True if it supports the attribute, false otherwise.
	*/
	function hasAttribute( $attributeName )
	{
		if ( isset( $this->attributeDefinition[$attributeName] ) )
		{
			return true;
		}

		return false;
	}

   /**
	* adds an attribute to the element's attribut3 collection. If the attribute
	* already exists, it is overwritten.
	*
	* @access     public
	* @param      string	$attributeName	The name of the attribute to add
	* @param      string	$attributeValue	The value of the attribute
	* @return     mixed	$success		True on success, a patError object otherwise
	*/
	function setAttribute( $attributeName, $attributeValue )
	{
		if ( !isset( $this->attributeDefinition[$attributeName] ) )
		{
			return patErrorManager::raiseNotice(
				PATFORMS_ELEMENT_NOTICE_ATTRIBUTE_NOT_SUPPORTED,
				'Unknown attribute ['.$attributeName.']',
				'Ignored the attribute as the ['.$this->elementName.'] element does not support it.'
			);
		}

		$this->attributes[$attributeName]	=	$attributeValue;

		return true;
	}

   /**
	* adds several attribute at once to the element's attributes collection.
	* Any existing attributes will be overwritten.
	*
	* @access     public
	* @param      array	$attributes	The attributes to add
	* @return     mixed	$success	True on success, false otherwise
	*/
	function setAttributes( $attributes )
	{
		if ( !is_array( $attributes ) )
		{
			return patErrorManager::raiseError(
				PATFORMS_ELEMENT_ERROR_ARRAY_EXPECTED,
				"Not an array given (setAttributes)"
			);
		}

		foreach ( $attributes as $attributeName => $attributeValue )
		{
			$this->setAttribute( $attributeName, $attributeValue );
		}

		return true;
	}

   /**
	* sets a renderer object that will be used to render
	* the element. Use the serialize() method to retrieve
	* the rendered content of the element.
	*
	* Only enabled in elements that support renderers, like
	* the radio element.
	*
	* @access     public
	* @param      object	&$renderer	The renderer object
	*/
	function setRenderer( &$renderer )
	{
		if ( !$this->usesRenderer )
		{
			return patErrorManager::raiseWarning(
				PATFORMS_ELEMENT_RENDERER_NOT_SUPPORTED,
				'The element \''.$this->elementName.'\' does not support the use of renderers - you do not have to set a renderer for this element.'
			);
		}

		if ( !is_object( $renderer ) )
		{
			return patErrorManager::raiseError(
				PATFORMS_ELEMENT_ERROR_INVALID_RENDERER,
				'You can only set a patForms_Renderer object with the setRenderer() method, "'.gettype( $renderer ).'" given.'
			);
		}

		$this->renderer	=	&$renderer;
	}

   /**
	* retrieves the value of an attribute.
	*
	* @access     public
	* @param      string	$attribute	The name of the attribute to retrieve
	* @return     mixed	$attributeValue	The value of the attribute, or false if it does not exist in the attributes collection.
	* @see        setAttribute()
	*/
	function getAttribute( $attribute )
	{
		if ( !isset( $this->attributes[$attribute] ) )
		{
			return false;
		}

		return $this->attributes[$attribute];
	}

   /**
	* retrieves all attributes, or only the specified attributes.
	*
	* @access     public
	* @param      array	$attributes	Optional: The names of the attributes to retrieve. Only the attributes that exist will be returned.
	* @return     array	$result		The attributes
	* @see        getAttribute()
	*/
	function getAttributes( $attributes = array() )
	{
		if ( empty( $attributes ) )
		{
			return $this->attributes;
		}

		$result	=	array();
		foreach ( $attributes as $attribute )
		{
			if ( $attributeValue = $this->getAttribute( $attribute ) )
			{
				$result[$attribute]	=	$attributeValue;
			}
		}

		return $result;
	}

   /**
	* Loads the default attribute values into the attributes collection. Done directly
	* on startup (in the consructor), so make sure you call this if your element needs
	* this feature and you have implemented a custom constructor in your element.
	*
	* @access     public
	* @return     bool	$success	Always returns true.
	* @see        $attributeDefaults
	*/
	function loadAttributeDefaults()
	{
		foreach ( $this->attributeDefinition as $attributeName => $attributeDef )
		{
			if ( isset( $attributeDef['default'] ) )
			{
				$this->attributes[$attributeName]	=	$attributeDef['default'];
			}
		}

		return true;
	}

   /**
	* retrieves the current value of the element. If none is set, will try to retrieve the
	* value from submitted form data.
	*
	* @access     public
	* @param      boolean		Determines whether the method is used from an external script
	* @return     mixed		The value, or an empty string if none found.
	* @see        setValue()
	* @see        value
	* @see        resolveValue()
	*/
	function getValue( $external = true )
	{
		if ( $this->value === false )
		{
			$this->resolveValue();

			// could not be resolved
			if ( $this->value === false )
			{
				$value	=	'';
			}
			else
			{
				$value	=	$this->value;
			}
		}
		else
		{
			$value	=	$this->value;
		}

		if ( $external === false )
		{
			return $value;
		}

		$value			=	$this->_applyFilters( $value, 'out', PATFORMS_FILTER_TYPE_PHP );

		return $value;
	}

   /**
	* resolves the scope the value of the element may be stored in, and returns it.
	*
	* @access     protected
	* @see        getValue()
	* @see        value
	* @todo       parse element name, if it uses the array syntax
	*/
	function resolveValue()
	{
		$varName	=	$this->attributes['name'];

		if ( $this->submitted && isset( $_POST[$varName] ) )
		{
			$this->value	=	$_POST[$varName];
			if ( ini_get( 'magic_quotes_gpc' ) )
				$this->value = $this->rStripSlashes( $this->value );
			$this->value	=	$this->_applyFilters( $this->value, 'in', PATFORMS_FILTER_TYPE_HTTP );
			return true;
		}

		if ( $this->submitted && isset( $_GET[$varName] ) )
		{
			$this->value	=	$_GET[$varName];
			if ( ini_get( 'magic_quotes_gpc' ) )
				$this->value = $this->rStripSlashes( $this->value );
			$this->value	=	$this->_applyFilters( $this->value, 'in', PATFORMS_FILTER_TYPE_HTTP );
			return true;
		}

		if ( isset( $this->attributes['default'] ) )
		{
			$this->value	=	$this->attributes['default'];
			$this->value	=	$this->_applyFilters( $this->value, 'in', PATFORMS_FILTER_TYPE_PHP );

			return true;
		}

		return true;
	}

   /**
	* recursively strip slashes
	*
	* This method is used to 'fix' magic_quotes_gpc.
	*
	* @access     public
	* @param      mixed		user input (get or post)
	* @return     mixed		data with slashes stripped
	*/
	function rStripSlashes( $value )
	{
		if ( is_scalar( $value ) )
			return stripslashes( $value );
		if ( is_array( $value ) )
		{
			foreach ( $value as $key => $val )
			{
				$value[$key] = $this->rStripSlashes( $val );
			}
		}
		return $value;
	}

   /**
	* apply filters to a value
	*
	* @access     private
	* @param      mixed		value
	* @param      string		direction of the filter ('in' or 'out')
	* @param      integer		type of filters to apply
	* @return     mixed		filtered value
	*/
	function _applyFilters( $value, $dir = 'in', $type = PATFORMS_FILTER_TYPE_PHP )
	{
		if ( empty( $this->filters ) )
			return $value;

			/**
			 * apply filters!
			 */
			$cnt	=	count( $this->filters );
			for ( $i = 0; $i < $cnt; $i++ )
			{
				/**
				 * check, whether filter is located between php script and form
				 */
				if ( $this->filters[$i]->getType() != $type )
				{
					continue;
				}

				$value	=	$this->filters[$i]->$dir( $value );
			}
		return $value;
	}

   /**
	* retrieves the current mode of the element
	*
	* @access     public
	* @return     string	$mode	The current element mode
	* @see        setMode()
	* @see        mode
	*/
	function getMode()
	{
		return $this->mode;
	}

   /**
	* retrieves the current format of the element
	*
	* @access     public
	* @return     string	$format	The current element format
	* @see        setFormat()
	* @see        format
	*/
	function getFormat()
	{
		return $this->format;
	}

   /**
	* retrieves the element's current submitted state.
	*
	* @access     public
	* @return     bool	$state	True if it has been submitted, false otherwise.
	* @see        setSubmitted()
	* @see        submitted
	*/
	function getSubmitted()
	{
		return $this->submitted;
	}

   /**
	* retrieves the name of the element
	*
	* @access     public
	* @return     string	$name	name of the element
	* @uses       getAttribute()
	*/
	function getName()
	{
		return $this->getAttribute( 'name' );
	}

   /**
	* add a custom validation rule
	*
	* @access     public
	* @param      object patForms_Rule	validation rule
	* @param      integer					time, when rule has to be applied, can be before or after validation.
	*									If set to null, it will use the default value as specified in the rule
	* @return     boolean					currently always true
	*/
	function addRule( &$rule, $time = null )
	{
		if ( is_null( $time ) )
		{
			$time	=	$rule->getTime();
		}

		$rule->prepareRule( $this );

		$this->_rules[]	= array(
									'rule'			=>	&$rule,
									'time'			=>	$time,
								);
		return true;
	}

   /**
	* adds an observer to the element
	*
	* @access     public
	* @param      object patForms_Observer	observer
	* @return     boolean						currently always true
	*/
	function attachObserver( &$observer )
	{
		$this->observers[] = &$observer;
		return true;
	}

   /**
	* dispatches the serialization of the element in the format that was set to the
	* corresponding method in the element class. These methods must be named in the
	* folowing scheme:
	*
	* serialize[format][mode](), e.g. serializeHtmlDefault()
	*
	* @access     public
	* @return     string	$element		The created element according to the specified mode.
	* @see        setFormat()
	* @see        setMode()
	* @todo       serialize*() methods should return a patError object instead of false!!!!
	*           Has to be changed asap!
	*/
	function serialize()
	{
		$methodName	=	"serialize".ucfirst( $this->getFormat() ).ucfirst( $this->getMode() );

		if ( !method_exists( $this, $methodName ) )
		{
			return patErrorManager::raiseError(
				PATFORMS_ELEMENT_ERROR_METHOD_FOR_MODE_NOT_AVAILABLE,
				"Element method for form mode '".$this->getMode()."' (".$methodName.") is not available."
			);
		}

		/**
		 * get the value for internal use
		 * The PHP-filters will not be applied
		 */
		$value	=	$this->getValue( false );

		$element = $this->$methodName( $value );
		if ( patErrorManager::isError( $element ) )
		{
			return $element;
		}

		return $element;
	}

   /**
	* Template method that applies rules and calls the elements
	* validation method
	*
	* @final
	* @access     public
	* @return     bool	$success	True on success, false otherwise
	*/
	function validate()
	{
		// apply locale, if the current locale is a custom locale
		if (patForms::isCustomLocale($this->locale)) {
			$cnt = count( $this->_rules );
			for ( $i = 0; $i < $cnt; $i++ ) {
				$this->_rules[$i]['rule']->setLocale($this->locale);
			}
		}

		/**
		 * validate custom rules
		 */
		if ( !$this->_applyRules( PATFORMS_RULE_BEFORE_VALIDATION ) )
		{
			$this->_announce( 'status', 'error' );
			return false;
		}

		/**
		 * the the unfiltered value
		 */
		$value	=	$this->getValue( false );

		$valid  =   $this->validateElement( $value );
		if ( $valid === false )
		{
			$this->_announce( 'status', 'error' );
			return false;
		}

		/**
		 * validate custom rules
		 */
		if ( !$this->_applyRules( PATFORMS_RULE_AFTER_VALIDATION ) )
		{
			$this->_announce( 'status', 'error' );
			return false;
		}

		$this->_announce( 'status', 'validated' );
		return true;
	}

   /**
	* validates the given data with the element's validation routines
	* and returns the data with any needed modifications.
	*
	* @abstract
	* @access     private
	* @return     bool	$success	True on success, false otherwise
	*/
	function validateElement()
	{
		// your code here
		return true;
	}

   /**
	* apply rules
	*
	* @access     private
	* @param      integer		time of validation
	* @return     boolean		rules are valid or not
	* @todo       add documentation
	*/
	function _applyRules( $time )
	{
		$valid	=	true;

		$cnt	=	count( $this->_rules );
		for ( $i = 0; $i < $cnt; $i++ )
		{
			if ( ( $this->_rules[$i]['time'] & $time ) != $time )
				continue;

			$result	=	$this->_rules[$i]['rule']->applyRule( $this, $time );
			if ( $result === false )
			{
				$valid	=	false;
			}
		}
		return	$valid;
	}

   /**
	* finalize the element.
	*
	* Used as a template method.
	*
	* @final
	* @access     protected
	* @return     bool	$success	True on success, false otherwise
	* @uses       finalizeElement() to call the user code
	*/
	function finalize()
	{
		$value	=	$this->getValue( false );
		return $this->finalizeElement( $value );
	}

   /**
	* finalize the element
	*
	* Offers the possibility to process any needed operations after the element
	* has been validated. Implement any tasks that you need to do then here - a
	* good example is the File element, where this method enables the moving of
	* the uploaded file to the correct location.
	*
	* @abstract
	* @access     private
	* @param      mixed	value of the element
	* @return     bool	$success	True on success, false otherwise
	*/
	function finalizeElement( $value )
	{
		return true;
	}

   /**
	* Enables an element option.
	*
	* See the {@link patForms::$options} property for an exhaustive list of available options.
	*
	* @access     public
	* @param      string	$option		The option to enable
	* @param      array	$params		Optional parameters for the option
	* @see        disableOption()
	* @see        $options
	*/
	function enableOption( $option, $params = array() )
	{
		if ( !isset( $this->options[$option] ) )
			$this->options[$option]	=	array();

		$this->options[$option]['enabled']	=	true;
		$this->options[$option]['params']	=	$params;
	}

   /**
	* Disables an element option
	*
	* See the {@link patForms::$options} property for an exhaustive list of available options.
	*
	* @access     public
	* @param      string	$option	The option to disable
	* @see        enableOption()
	* @see        $options
	*/
	function disableOption( $option )
	{
		if ( !isset( $this->options[$option] ) )
			$this->options[$option]	=	array();

		$this->options[$option]['enabled']	=	false;
	}

   /**
	* [helper method] validates the given value according to the specified method. It first
	* checks if there is a method to check the format in the {@link patForms_FormatChecker}
	* class, then checks in the element class itself.
	*
	* @access     public
	* @param      mixed	$value		The value to validate the format of
	* @param      string	$format		The format to validate the value with
	* @return     bool	$isValid	True if valid, false if invalid or no method exists to validate the format.
	* @see        patForms_FormatChecker
	*/
	function validateFormat( $value, $format )
	{
		if ( !class_exists( "patForms_FormatChecker" ) )
		{
			$checkerFile	=	dirname( __FILE__ )."/FormatChecker.php";
			if ( !file_exists( $checkerFile ) )
			{
				$this->valid	=	false;
				return patErrorManager::raiseError(
					PATFORMS_ELEMENT_ERROR_FORMAT_CHECKER_NOT_FOUND,
					"Type checker could not be found, aborting validation."
				);
			}

			include_once( $checkerFile );
		}

		$format	=	strtolower( $format );

		$methodName	=	"is_".$format;
		$option		=	false;

		if ( method_exists( $this, $methodName ) )
		{
			return $this->$methodName( $value );
		}

		if ( in_array( $methodName, get_class_methods( "patForms_FormatChecker" ) ) )
		{
			return call_user_func( array( 'patForms_FormatChecker', $methodName ), $value );
		}

		return false;
	}

   /**
	* get next error offset
	*
	* @access     public
	* @return     integer
	*/
	function getErrorOffset( $requiredCodes = 100 )
	{
		$offset					= $this->nextErrorOffset;
		$this->nextErrorOffset	= $this->nextErrorOffset + $requiredCodes;
		return $offset;
	}

   /**
	* add error codes and messages for validator method
	*
	* @access     public
	* @param      array	defintions
	* @param      integer	offset for the error codes
	*/
	function addValidatorErrorCodes( $defs, $offset = 1000 )
	{
		foreach ( $defs as $lang => $codes )
		{
			if ( !isset( $this->validatorErrorCodes[$lang] ) ) {
				$this->validatorErrorCodes[$lang]	=	array();
			}

			foreach ( $codes as $code => $message ) {
				$this->validatorErrorCodes[$lang][($offset+$code)]	=	$message;
			}
		}
	}

	/**
	* getValidationErrors
	*
	* @access     public
	* @return     array	errors that occured during the validation
	*/
	function    getValidationErrors()
	{
		return  $this->validationErrors;
	}

	/**
	* addValidationError
	*
	*
	* @access     public
	* @param      integer	$code
	* @param      array	$vars	fill named placeholder with values
	* @return     boolean $result	true on success
	*/
	function    addValidationError( $code, $vars = array() )
	{
		$error		=	false;
		$lang		=	$this->locale;
		$element	=	$this->getElementName();

		// find error message for selected language
		while ( true )
		{
			// error message matches language code
			if ( isset( $this->validatorErrorCodes[$lang][$code] ) )
			{
				$error	=	array( "element" => $element, "code" => $code, "message" => $this->validatorErrorCodes[$lang][$code] );
				break;
			}
			// no message found and no fallback-langauage available
			else if ( $lang == "C" )
			{
				break;
			}

			$lang_old	=	$lang;

			// look for other languages
			if ( strlen( $lang ) > 5 )
			{
				list( $lang, $trash	) =	explode( ".", $lang );
			}
			else if ( strlen( $lang ) > 2 )
			{
				list( $lang, $trash	) =	explode( "_", $lang );
			}
			else
			{
				$lang	=	"C";
			}

			// inform developer about missing language
			patErrorManager::raiseNotice(
				PATFORMS_ELEMENT_ERROR_VALIDATOR_ERROR_LOCALE_UNDEFINED,
				"Required Validation Error-Code for language: $lang_old not available. Now trying language: $lang",
				"Add language definition in used element or choose other language"
			);

		}

		// get default Error!
		if ( !$error )
		{
	 		patErrorManager::raiseWarning(
				PATFORMS_ELEMENT_ERROR_VALIDATOR_ERROR_UNDEFINED,
				"No Error Message for this validation Error was defined",
				"Review the error-definition for validation-errors in your element '$element'."
			);
			$error	=	array( "element" => $element, "code" => 0, "message" => "Unknown validation Error" );
		}

		// insert values to placeholders
		if ( !empty( $vars ) )
		{
			foreach ( $vars as $key => $value )
			{
				$error["message"]	=	str_replace( "[". strtoupper( $key ) ."]", $value, $error["message"] );
			}
		}

		array_push( $this->validationErrors, $error );
		$this->valid	=	false;
		return  true;
	}

   /**
	* applies the specified modifiers to an attribute value, as set in the attribute definition.
	*
	* @access     private
	* @param      mixed	$attributeValue	The value of the attribute to modify
	* @param      array	$modifiers		Array containing the list of modifiers and their options to apply.
	* @return     mixed	$attributeValue	The modified attribute value.
	* @see        createAttributes()
	*/
	function _applyModifiers( $attributeValue, $modifiers )
	{
		if ( !is_array( $modifiers ) )
		{
			return patErrorManager::raiseError(
				PATFORMS_ELEMENT_ERROR_MODIFIER_NOT_AN_ARRAY,
				"Modifiers are not an array"
			);
		}

		foreach ( $modifiers as $modifier => $modifierOptions )
		{
			// compute method name for this definition and check if it exists
			$modifierMethod	=	"_modifier".ucfirst( $modifier );

			if ( !method_exists( $this, $modifierMethod ) )
			{
				return patErrorManager::raiseError(
					PATFORMS_ELEMENT_ERROR_METHOD_FOR_MODIFIER_NOT_FOUND,
					"Method not found for modifier '" . $modifier . "' (".$modifierMethod.") in class '" . get_class( $this ) . "'"
				);
			}

			$modifiedValue	=	$this->$modifierMethod( $attributeValue );

			if ( $modifiedValue === false )
			{
				return patErrorManager::raiseError(
					PATFORMS_ELEMENT_ERROR_MODIFIER_RETURNED_ERROR,
					"Modifier '".$modifier."' returned an error."
				);
			}

			$attributeValue	=	$modifiedValue;
		}

		return $attributeValue;
	}

   /**
	* insertSpecials attribute value modifier
	*
	* you can use special placeholders to insert dynamic values into the attribute values.
	* This method inserts the correct information for each placeholder in the given string.
	*
	* @access     private
	* @param      string	$string	The string to insert the specials in
	* @return     string	$string	The string with all needed replacements
	* @see        _applyModifiers()
	* @todo       Maybe make this configurable
	* @todo       Add any other relevant information
	*/
	function _modifierInsertSpecials( $modifyValue, $options = array() )
	{
		if ( is_array( $modifyValue ) || is_object( $modifyValue ) || is_array( $this->value ) )
			return $modifyValue;

		// go through each attribute in the attribute definition and replace the strings
		// with the corresponding attribute values.
		foreach ( $this->attributeDefinition as $attributeName => $attributeDef )
		{
			// if attribute was not set, strip the variable by setting it to empty.
			$attributeValue	=	"";

			// retrieve real attribute value if it was set
			if ( isset( $this->attributes[$attributeName] ) && is_string( $this->attributes[$attributeName] ) )
			{
				$attributeValue	=	$this->attributes[$attributeName];
			}

			$search	=	$this->modifierStart."ELEMENT_".strtoupper( $attributeName ).$this->modifierEnd;

			// make the replacement
			$modifyValue	=	str_replace( $search, $attributeValue, $modifyValue );
		}

		// the element's value is special...
		$modifyValue	=	str_replace( $this->modifierStart."ELEMENT_VALUE".$this->modifierEnd, $this->value, $modifyValue );

		return $modifyValue;
	}

   /**
	* checks the format of an attribute value according to the given format.
	*
	* @access     private
	* @param      mixed	$attributeValue	The attribute value to check
	* @param      string	$format			The format to check the attribute value against
	* @return     bool	$result			True if format check succeeded, false otherwise.
	* @see        createAttributes()
	* @todo       Implement this method sometime
	*/
	function _checkAttributeFormat( $attributeValue, $format )
	{
		return true;
	}

   /**
	* validates the current attribute collection according to the attributes definition
	* and the given output format, and returns the list of valid attributes.
	*
	* @access     private
	* @param      string	$format		The output format to retrieve the attributes for.
	* @return     mixed	$attributes	The list of attributes, or false if failed.
	*/
	function getAttributesFor( $format )
	{
		$attributes	=	array();

		foreach ( $this->attributeDefinition as $attributeName => $attributeDef )
		{
			if ( !isset( $this->attributes[$attributeName] ) )
			{
				if ( $attributeDef["required"] )
				{
					return patErrorManager::raiseError(
						PATFORMS_ELEMENT_ERROR_ATTRIBUTE_REQUIRED,
						'The element "'.$this->getElementName().'" needs the attribute "'.$attributeName.'" to be set.',
						'See the attribute definition of the element class "'.get_class( $this ).'"'
					);
				}

				continue;
			}

			$attributeValue	=	$this->attributes[$attributeName];

			// special case disabled attribute: skip this if it is not set to yes
			// to avoid generating a disabled field anyway (empty HTML attribute)
			if ( $attributeName == 'disabled' && $attributeValue != 'yes' )
			{
				continue;
			}

			if ( isset( $attributeDef["modifiers"] ) && !empty( $attributeDef["modifiers"] ) )
			{
				$modifiedValue	=	$this->_applyModifiers( $attributeValue, $attributeDef["modifiers"] );
				if ( $modifiedValue === false )
				{
					return patErrorManager::raiseError(
						PATFORMS_ELEMENT_ERROR_UNABLE_TO_APPLY_MODIFIER_TO_ATTRIBUTE,
						"Could not apply modifier to attribute '".$attributeName."' (value:'".$attributeValue."')"
					);
				}

				$attributeValue	=	$modifiedValue;

				// store this for later use too
				$this->attributes[$attributeName]	=	$attributeValue;
			}

			if ( !in_array( $format, $attributeDef["outputFormats"] ) )
			{
				continue;
			}

			if ( isset( $attributeDef["format"] ) )
			{
				if ( !$this->_checkAttributeFormat( $attributeValue, $attributeDef["format"] ) )
				{
					return patErrorManager::raiseError(
						PATFORMS_ELEMENT_ERROR_CAN_NOT_VERIFY_FORMAT,
						"Format '".$attributeDef["format"]."' could not be verified for attribute '".$attributeName."' => '".$attributeValue."'"
					);
				}
			}

			$attributes[$attributeName]	=	$attributeValue;
		}

		return $attributes;
	}

   /**
	* [helper method] wrapper for the {@link createTag()} method which automates the tag
	* creation by creating the tag from the current attribute collection and element type.
	*
	* @access     protected
	* @return     mixed	$result	The created tag, or false if failed.
	* @see        elementType
	* @see        attributes
	* @see        createTag()
	*/
	function toHtml()
	{
		$attributes	= $this->getAttributesFor( $this->getFormat() );
		if ( patErrorManager::isError( $attributes ) )
		{
			return $attributes;
		}

		return $this->createTag( $this->elementType[$this->getFormat()], "full", $attributes );
	}

   /**
	* [helper method] create a hidden field with the given value. Retrieves all other needed
	* attributes from the attributes collection.
	* @access     public
	*/
	function createHiddenTag( $value )
	{
		$attribs	=	array(	'type'	=>	'hidden',
								'name'	=>	$this->attributes['name'],
								'value'	=>	$value,
								'id'	=>	$this->attributes['id'],
							);

		return $this->createTag( "input", "full", $attribs );
	}

   /**
	* [helper method] creates a hidden field with the given value. Used for the
	* display=no attribute, and is the same as the createHiddenTag() method, only
	* that the attributes collection is initialized to ensure that any variables
	* in the element's attributes  get replaced.
	*
	* @access     private
	* @param      mixed	$value		The value of the element
	* @return     string	$element	The serialized hidden tag
	* @see        createHiddenTag()
	*/
	function createDisplaylessTag( $value )
	{
		// call this to initialize all attributes. This is needed
		// here to make sure that if there are
		$this->getAttributesFor( $this->getFormat() );

		return $this->createHiddenTag( $value );
	}

   /**
	* [helper method] create an element HTML source from its attribute collection and
	* returns it.
	*
	* @static
	* @access     protected
	* @param      string	$tagname		The name of the element / tag
	* @param      string	$type			Optional: the type of element to generate. Valid parameters are full|opening|closing|empty. Defaults to "full".
	* @param      mixed	$value			The value of the element
	* @return     string	$element		The HTML source of the element
	*/
	function createTag( $tagname, $type = "full", $attributes = array(), $value = false )
	{
		switch( $type )
		{
			case "closing":
				return	"</$tagname>";
				break;

			case "empty":
			case "opening":
				$tag	=	"<".$tagname;

				// create attribute collection
				foreach ( $attributes as $attributeName => $attributeValue )
				{
					$tag	=	$tag . " ".$attributeName."=\"".htmlentities( (string)$attributeValue )."\"";
				}

				// empty tag?
				if ( $type == "empty" )
				{
					$tag	=	$tag . " />";
					return	$tag;
				}

				$tag	=	$tag . ">";
				return	$tag;

				break;

			case "full":
				if ( $value === false )
				{
					return patForms_Element::createTag( $tagname, "empty", $attributes );
				}

				return patForms_Element::createTag( $tagname, "opening", $attributes ).htmlentities( $value ).patForms_Element::createTag( $tagname, "closing" );
				break;
		}
	}

   /**
	* create XML representation of the element
	*
	* This can be used when you need to store the structure
	* of your form in flat files or create form templates that can
	* be read by patForms_Parser at a later point.
	*
	* @access     public
	* @param      string		namespace
	* @uses       getElementName()
	* @see        patForms_Parser
	*/
	function toXML( $namespace = null )
	{
		$tagName	=	$this->getElementName();

		// prepend Namespace
		if ( $namespace != null )
		{
			$tagName	=	$namespace.':'.$tagName;
		}

		// get all attributes
		$attributes	=	$this->getAttributes();

		// create valid XML attributes
		foreach ( $attributes as $key => $value )
		{
			$attributes[$key]	=	strtr( $value, $this->xmlEntities );
		}

		$value	=	strtr( $this->getValue(), $this->xmlEntities );

		if ( $value != false )
		{
			return	$this->createTag( $tagName, "full", $attributes, $value );
		}
		return	$this->createTag( $tagName, "empty", $attributes );
	}

   /**
	* apply a filter
	*
	* This is still in alpha state!
	*
	* @access     public
	* @param      object patForms_Filter
	* @todo       add error management and docs
	* @todo       allow filter to be an array containg two callbacks
	*			array( 'in' => 'myInFunc', 'out' => 'myOutFunc' ) )
	*/
	function applyFilter( &$filter )
	{
		$this->filters[]	=	&$filter;
		return true;
	}

   /**
	* Get the name of the element, as stored in the elementName property.
	*
	* This is used when serializing an element to XML to
	* create a now form template.
	*
	* This method checks for the $elementName property and if it
	* is set to null, it will extract the element name from the class name
	*
	* @access     public
	* @return     string		tag name
	*/
	function getElementName()
	{
		if ( $this->elementName != null )
		{
			return	$this->elementName;
		}

		$class	=	get_class( $this );
		$name	=	substr( strrchr( $class, "_" ), 1 );
		return	ucfirst( $name );
	}

   /**
	* checks wheter sessions are used or switch session usage on or of
	*
	* If switch argument is missing, this function just reports if sessions
	* will be used or not
	*
	* @access     protected
	* @param      string $switch switch sessions on ("yes") or off ("yes")
	* @return     boolean $result true if sessions will be used, false otherwise
	* @see        setSessionValue()
	* @see        getSessionValue()
	* @see        unsetSessionValue()
	* @todo       destroy session variables if sessions won't be usead any further
	*/
	function useSession( $switch = null )
	{
		// switch sessions on or off
		if ( $switch == "yes" )
		{
			$this->attributes["usesession"]	=	"yes";
		}
		else if ( $switch == "no" )
		{
			$this->attributes["usesession"]	=	"no";
			return false;
		}

		if ( isset( $this->attributes["usesession"] ) && $this->attributes["usesession"] == "yes" )
		{
			if ( !$this->sessionVar )
			{
				if ( !defined( "SID" ) )
				{
					session_start();
				}

				$name	=	$this->attributes["name"];
				if ( !isset( $_SESSION["_patforms_element"][$name] ) )
				{
					$_SESSION["_patforms_element"][$name]	=	array();
				}

				$this->sessionVar	=&	$_SESSION["_patforms_element"][$name];
			}

			return true;
		}
		return false;
	}

   /**
	* save a variable to the session
	*
	* @access     protected
	* @param      string $name name to identify the variable
	* @param      mixed $value
	* @return     boolean $result true on success
	* @see        getSessionValue()
	* @see        unsetSessionValue()
	*/
	function setSessionValue( $name, $value )
	{
		if ( !$this->useSession() )
		{
			return false;
		}

		$this->sessionVar[$name]	=	$value;
		return true;
	}

   /**
	* get a variable from session
	*
	* @access     protected
	* @param      string $name name to identify the variable
	* @return     mixed $result false if no sessions are used, null if variable is not set or the value of the variable
	* @see        getSessionValue()
	* @see        unsetSessionValue()
	*/
	function getSessionValue( $name )
	{
		if ( !$this->useSession() )
		{
			return false;
		}

		if ( isset( $this->sessionVar[$name] ) )
		{
			return $this->sessionVar[$name];
		}
		return null;
	}

   /**
	* remove a variable from session
	*
	* @access     protected
	* @param      string $name name to identify the variable
	* @return     mixed $result false if no sessions are used, null if variable is not set or the value of the variable
	* @see        getSessionValue()
	* @see        setSessionValue)
	*/
	function unsetSessionValue( $name )
	{
		if ( !$this->useSession() )
		{
			return false;
		}

		$value	=	null;
		if ( isset( $this->sessionVar[$name] ) )
		{
			$value	=	$this->sessionVar[$name];
			unset( $this->sessionVar[$name] );
		}
		return $value;
	}

   /**
	* get the global javascript of the element
	*
	* @access     public
	* @return     string
	*/
	/*
	function getGlobalJavascript()
	{
		if ( !isset( $this->globalJavascript[$this->format] ) )
		{
			$script	=	'';
		}
		else
		{
			$script	=	$this->globalJavascript[$this->format];
		}

		$cnt	=	count( $this->_rules );
		for ( $i = 0; $i < $cnt; $i++ )
		{
			$tmp	=	$this->_rules[$i]['rule']->getGlobalJavascript();
			if ( $tmp === false )
				continue;
			$script	.=	$tmp;
		}

		return $script;
	}
	*/

   /**
	* get the instance javascript of the element
	*
	* @access     public
	* @return     string	javascript for this instance
	*/
	/*
	function getInstanceJavascript()
	{
		if ( !isset( $this->instanceJavascript[$this->format] ) )
		{
			$script	=	'';
		}
		else
		{
			$script	=	$this->instanceJavascript[$this->format];

			$script	=	str_replace( '[ELEMENT::NAME]', $this->getName(), $script );
			$script	=	str_replace( '[ELEMENT::ID]', $this->getId(), $script );
		}

		$cnt	=	count( $this->_rules );
		for ( $i = 0; $i < $cnt; $i++ )
		{
			$tmp	=	$this->_rules[$i]['rule']->getInstanceJavascript();
			if ( $tmp === false )
				continue;
			$script	.=	$tmp;
		}

		return $script;
	}
	*/

	function registerJavascripts(&$form) {

		if ($script = $this->getGlobalJavascript()) {
			$form->registerGlobalJavascript($this->elementName, $script);
		}

		if ($script = $this->getInstanceJavascript()) {
			$form->registerInstanceJavascript($script);
		}

		foreach ($this->_rules as $rule) {
			$rule['rule']->registerJavascripts($form);
		}
	}

	function getGlobalJavascript() {

		if (isset($this->globalJavascript[$this->format])) {
			return $this->globalJavascript[$this->format];
		}
	}

	function getInstanceJavascript() {

		if (isset($this->instanceJavascript[$this->format])) {
			$script	= $this->instanceJavascript[$this->format];
			$script	= str_replace('[ELEMENT::NAME]', $this->getName(), $script);
			$script	= str_replace('[ELEMENT::ID]', $this->getId(), $script);
			return $script;
		}
	}

   /**
	* retrieves the element's current submitted state.
	*
	* @access     public
	* @return     bool	$state	True if it has been submitted, false otherwise.
	* @see        submitted
	*/
	function isSubmitted()
	{
		if ( $this->submitted === true ) {
			return true;
		}
		return false;
	}

   /**
	* returns the locale that is currently set for the form.
	*
	* @access     public
	* @return     string	$locale	The locale.
	* @see        setLocale()
	* @see        $locale
	*/
	function getLocale()
	{
		return $this->locale;
	}

   /**
	* anounce a change in the element to all observers
	*
	* @access     private
	* @param      string		property that changed
	* @param      mixed		new value of the property
	*/
	function _announce( $property, $value )
	{
		$cnt = count( $this->observers );
		for ( $i = 0; $i < $cnt; $i++ )
		{
			$this->observers[$i]->notify( $this, $property, $value );
		}
		return true;
	}
}
