<?php
/**
 * patForms form manager class - serialize form elements into any given output format
 * using element classes, and build the output via renderer classes.
 *
 * $Id: patForms.php 7215 2006-06-17 15:01:54Z chregu $
 *
 * @package		patForms
 * @author		Sebastian Mordziol <argh@php-tools.net>
 * @author		gERD Schaufelberger <gerd@php-tools.net>
 * @author		Stephan Schmidt <schst@php-tools.net>
 * @copyright	2003-2004 PHP Application Tools
 * @license		LGPL
 * @link		http://www.php-tools.net
 */

/**
 * set the include path
 */
if( !defined( 'PATFORMS_INCLUDE_PATH' ) ) {
	define( 'PATFORMS_INCLUDE_PATH', dirname( __FILE__ ). '/patForms' );
}



/**
 * location of global javascripts
 */
define('PATFORMS_SCRIPT_PATH', PATFORMS_INCLUDE_PATH . '/Scripts');

/**
 * needs helper methods of patForms_Element
 */
include_once PATFORMS_INCLUDE_PATH . "/Element.php";
 
/**
 * error definition: renderer base class file (renderers/_base.php) could not 
 * be found.
 *
 * @see	patForms::_createModule()
 */									
define( "PATFORMS_ERROR_NO_MODULE_BASE_FILE", 1001 );

/**
 * error definition: the specified renderer could not be found.
 *
 * @see	patForms::_createModule()
 */									
define( "PATFORMS_ERROR_MODULE_NOT_FOUND", 1002 );

/**
 * error definition: the element added via the {@link patForms::addElement()}
 * is not an object. Use the {@link patForms::createElement()} method to
 * create an element object.
 *
 * @see	patForms::addElement()
 * @see	patForms::createElement()
 */									
define( "PATFORMS_ERROR_ELEMENT_IS_NO_OBJECT", 1003 );

/**
 * error definition: generic unexpected error.
 */									
define( "PATFORMS_ERROR_UNEXPECTED_ERROR", 1004 );

/**
 * element does not exist
 */									
define( "PATFORMS_ERROR_ELEMENT_NOT_FOUND", 1012 );

/**
 * renderer object has not been set - if you want to render the form, you have to
 * set a renderer object via the {@link patForms::setRenderer()} method. To create
 * a renderer, use the {@link patForms::createRenderer()} method.
 *
 * @see	patForms::setRenderer()
 * @see	patForms::createRenderer()
 */									
define( "PATFORMS_ERROR_NO_RENDERER_SET", 1013 );

/**
 * invalid renderer
 *
 * @see	createRenderer()
 */									
define( "PATFORMS_ERROR_INVALID_RENDERER", 1014 );

/**
 * invalid method
 *
 * @see	setMethod()
 */									
define( "PATFORMS_ERROR_INVALID_METHOD", 1015 );

/**
 * Given parameter is not a boolean value
 */									
define( "PATFORMS_ERROR_PARAMETER_NO_BOOL", 1016 );

/**
 * Given Static property does not exist
 */
define( "PATFORMS_ERROR_NO_STATIC_PROPERTY", 1017 );

/**
 * Unknown event
 */
define( "PATFORMS_ERROR_UNKNOWN_EVENT", 1018 );

/**
 * Invalid event handler
 */
define( "PATFORMS_ERROR_INVALID_HANDLER", 1019 );

/**
 * Event exists
 */
define( 'PATFORMS_NOTICE_EVENT_ALREADY_REGISTERED', 1020 );

/**
 * Invalid storage container
 */
define( 'PATFORMS_ERROR_INVALID_STORAGE', 1021 );

define( 'PATFORMS_NOTICE_ARRAY_EXPECTED', 1022 );

define( 'PATFORMS_NOTICE_ATTRIBUTE_NOT_SUPPORTED', 1023 );

define( 'PATFORMS_NOTICE_INVALID_OPTION', 1024 );

define( 'PATFORMS_ERROR_ATTRIBUTE_REQUIRED', 1025 );

define( 'PATFORMS_ERROR_CAN_NOT_VERIFY_FORMAT', 1026 );

define( 'PATFORMS_ERROR_METHOD_FOR_MODE_NOT_AVAILABLE', 1027 );


/**
 * errors apply on translating errors matching current locale settings
 */
define( 'PATFORMS_NOTICE_VALIDATOR_ERROR_LOCALE_UNDEFINED', 1028 );
define( 'PATFORMS_WARNING_VALIDATOR_ERROR_UNDEFINED', 1029 );

/**
 * Script file could not be loaded
 */
define( 'PATFORMS_WARNING_SCRIPTFILE_NOT_FOUND', 1040 );

/**
 * apply the rule before the built-in validation
 */
define( 'PATFORMS_RULE_BEFORE_VALIDATION', 1 );
 
/**
 * apply the rule after the built-in validation
 */
define( 'PATFORMS_RULE_AFTER_VALIDATION', 2 );

/**
 * apply the rule before AND after the built-in validation
 */
define( 'PATFORMS_RULE_BOTH', 3 );
    
/**
 * attach the observer to the elements
 */
define( 'PATFORMS_OBSERVER_ATTACH_TO_ELEMENTS', 1 );

/**
 * attach the observer to the form
 */
define( 'PATFORMS_OBSERVER_ATTACH_TO_FORM', 2 );

/**
 * attach the observer to the form and the elements
 */
define( 'PATFORMS_OBSERVER_ATTACH_TO_BOTH', 3 );

/**
 * group values should stay nested
 */
define('PATFORMS_VALUES_NESTED', 0);

/**
 * group values should be flattened
 */
define('PATFORMS_VALUES_FLATTENED', 1);

/**
 * group values should be prefixed
 */
define('PATFORMS_VALUES_PREFIXED', 2);

/**
 * Static patForms properties - used to emulate pre-PHP5 static properties.
 * 
 * @see	setStaticProperty()
 * @see	getStaticProperty()
 */
$GLOBALS['_patForms']	=	array(
	'format'			=>	'html',
	'locale'			=>	'C',
	'customLocales'		=>	array(),
	'autoFinalize'		=>	true,
	'defaultAttributes'	=>	array(),
	'elementCounter'	=>	0,
);

/**
 * patForms form manager class - serialize form elements into any given output format
 * using element classes, and build the output via renderer classes.
 *
 * @package		patForms
 * @author		Sebastian Mordziol <argh@php-tools.net>
 * @author		gERD Schaufelberger <gerd@php-tools.net>
 * @author		Stephan Schmidt <schst@php-tools.net>
 * @copyright	2003-2004 PHP Application Tools
 * @license		LGPL
 * @link		http://www.php-tools.net
 * @version		1.0
 * @todo		check the clientside functionality, as that can lead to broken pages
 */
class patForms
{
   /**
	* javascript that will 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();

   /**
	* stores the mode for the form. It defaults to 'default', and is only overwritten if
	* set specifically. It is passed on to any elements you create.
	*
	* @access	private
	* @see		setMode()
	*/
	var $mode	=	'default';
	
   /**
	* XML entities
	*
	* @access	private
	* @see		toXML()
	* @todo		This is redundant to the Element's xmlEntities property - find a way to keep this in one place
	*/
	var $xmlEntities = array(
		"<"	=>	"&lt;",
		">"	=>	"&gt;",
		"&"	=>	"&amp;",
		"'"	=>	"&apos;",
		'"'	=>	"&quot;"
	);

   /**
	* stores the format for the element. It defaults to 'html', and is only overwritten if
	* set specifically. It is passed on to any elements you create.
	*
	* @access	private
	* @see		setFormat()
	*/
	var $format	=	'html';
	
   /**
	* stores the flag telling the form whether it has been submitted - this is passed on to any
	* elements you create.
	*
	* @access	private
	* @see		setSubmitted()
	*/
	var $submitted	=	false;

   /**
	* stores the element objects of this form.
	* @access	private
	* @see		addElement()
	*/
	var $elements	=	array();
	
   /**
	* stores a renderer
	* @access	private
	* @see		setRenderer(), renderForm()
	*/
	var $renderer		=	null;
	
   /**
	* stores the locale to use when adding validation errors for the whole form.
	*
	* @access	private
	* @var		string	$locale
	* @see		setLocale()
	*/
	var	$locale		=	'C';
	
   /**
	* stores custom locale
	*
	* @access	private
	* @var		array
	* @see		setLocale()
	*/
	var	$customLocales = array();

   /**
	* stores the element name
	* @access	private
	* @see		getElementName()
	*/
	var $elementName = 'Form';
	
   /**
	* flag to indicate, whether form should be validated automatically
	* by renderForm()
	*
	* @access	private
	* @var		string
	* @see		setAutoValidate(), renderForm()
	*/
	var	$autoValidate	=	false;
	
   /**
	* name of the variable that indicates, whether the form has
	* been submitted.
	*
	* @access	private
	* @var		string
	* @see		setAutoValidate()
	*/
	var	$submitVar	=	null;
	
   /**
	* event handlers
	*
	* @access	private
	* @var		array
	* @see		registerEventHandler()
	* @see		registerEvent()
	*/
	var	$_eventHandler	=	array();
	
   /**
	* events that can be triggered
	*
	* @access	private
	* @var		array
	* @see		registerEventHandler()
	* @see		triggerEvent()
	* @see		registerEvent()
	*/
	var	$_validEvents	=	array( 'onInit', 'onSubmit', 'onError', 'onSuccess' );
	
   /**
	* Stores whether the current form has been validated
	*
	* @access	private
	*/
	var $validated	=	false;
	
   /**
	* Stores whether the current form is valid or not (after the 
	* validation process)
	*
	* @access	private
	*/
	var $valid	=	null;
	
   /**
	* Stores the names of all static properties that patForms will use as defaults
	* for the properties with the same name on startup.
	*
	* @access	private
	*/
	var $staticProperties	=	array(
		'format'		=>	'setFormat',
		'autoFinalize'	=>	'setAutoFinalize',
		'locale'		=>	'setLocale',
	);
	
   /**
	* Stores the flag for the autoFinalize feature
	*
	* @access	private
	*/
	var $autoFinalize	=	true;

   /**
	* custom validation rules
	* 
	* @access	private
	* @var		array
	*/
	var $_rules			=	array();
	
   /**
	* define error codes an messages for the form
	*
	* Will be set by validation rules that have been
	* added to the form.
	*
	* @access private
	* @var	array	$validatorErrorCodes
	*/
	var	$validatorErrorCodes  =   array();

   /**
	* stores any validation errors that can occurr during the
	* form's validation process.
	*
	* @access	private
	* @var		array	$validationErrors
	*/
	var	$validationErrors  =   array();
	
   /**
	* next error offset for rules
	* @access	private
	* @var		integer
	*/
	var $nextErrorOffset	=	1000;

   /**
	* Attributes of the form - needed to generate the form tag
	*
	* @access	private
	* @var		array	$attributes
	* @see		setAttribute()
	*/
	var	$attributes	=	array();
	
   /**
	* Attribute definition for the form - defines which attribute the form
	* itself supports.
	*
	* @access	public
	*/
	var	$attributeDefinition	=	array(	
			
		'id' =>	array(	
			'required'		=>	false,
			'format'		=>	'string',
			'outputFormats'	=>	array( 'html' ),
		),
		
		'name' => array(	
			'required'		=>	true,
			'format'		=>	'string',
			'outputFormats'	=>	array( 'html' ),
		),
		
		'method' => array(	
			'required'		=>	true,
			'format'		=>	'string',
			'default'		=>	'post',
			'outputFormats'	=>	array( 'html' ),
		),

		'action' => array(	
			'required'		=>	true,
			'format'		=>	'string',
			'outputFormats'	=>	array( 'html' ),
		),

		'accept' => array(	
			'required'		=>	false,
			'format'		=>	'string',
			'outputFormats'	=>	array( 'html' ),
		),

		'accept-charset' => array(	
			'required'		=>	false,
			'format'		=>	'string',
			'outputFormats'	=>	array( 'html' ),
		),

		'enctype' => array(	
			'required'		=>	false,
			'format'		=>	'string',
			'outputFormats'	=>	array( 'html' ),
		),

		'onreset' => array(	
			'required'		=>	false,
			'format'		=>	'string',
			'outputFormats'	=>	array( 'html' ),
		),

		'onsubmit' => array(	
			'required'		=>	false,
			'format'		=>	'string',
			'outputFormats'	=>	array( 'html' ),
		),

		'target' => array(	
			'required'		=>	false,
			'format'		=>	'string',
			'outputFormats'	=>	array( 'html' ),
		),
	);
	
   /**
	* Stores all available patForms options - these are inherited by all elements
	* and their dependencies, like rules.
	*
	* Short option overview:
	*
	* - scripts: enable client script integration
	*
	* @access	public
	*/
	var $options	=	array(

		'scripts'	=>	array(
			'enabled'	=>	false,
			'params'	=>	array(
									'folder'    => PATFORMS_SCRIPT_PATH,
									'jsInclude' => false
								),
		),

	);
	
   /**
	* observers of the form
	*
	* @access	private
	* @var		array
	*/
	var	$observers = array();

   /**
	* Sets the default attributes that will be inherited by any elements you add to the form.
	*
	* <b>Note:</b> You have to call this method statically before creating a new form if you use 
	* patForm's automatic element creation feature via the {@link createForm()} method, as the
	* default attributes cannot be set after an element has been created.
	*
	* @static
	* @access	public
	* @param	array	$attributes	The list of attributes to set with key => value pairs.
	*/
	function setDefaultAttributes( $attributes )
	{
		patForms::setStaticProperty( 'defaultAttributes', $attributes );
	}
	
   /**
	* sets the locale (language) to use for the validation error messages of all elements
	* in the form.
	*
	* @access	public
	* @param	string		language code
	* @param	string		optional language file
	* @return	bool		True on success
	*/
	function setLocale( $locale, $languageFile = null )
	{
		if (!is_null($languageFile)) {
			$languageData   = patForms::parseLocaleFile($languageFile);

			$customLocales = patForms::getStaticProperty('customLocales');
			$customLocales[$locale] = $languageData;
			patForms::setStaticProperty('customLocales', $customLocales);
		}

		if( isset( $this ) && is_a( $this, 'patForms' ) ) {
			$this->locale = $locale;
		
			if( !empty( $this->elements ) ) {
				$cnt	=	count( $this->elements );
				for( $i=0; $i < $cnt; $i++ ) {
					$this->elements[$i]->setLocale( $locale );
				}
			}
		} else {
			patForms::setStaticProperty('locale', $locale);
		}
		
		return true;
	}

   /**
	* checks, whether a locale is a custom locale
	*
	* @static
	* @access	public
	* @param	string		locale name
	* @return	boolean
	*/
	function isCustomLocale($locale)
	{
		$customLocales = patForms::getStaticProperty('customLocales');
		if (isset($customLocales[$locale])) {
			return true;
		}
		return false;
	}

   /**
	* get the custom locale for an element or a rule
	*
	* @static
	* @access	public
	* @param	string		locale
	* @param	string		key
	* @return	array
	*/
	function getCustomLocale($locale, $key)
	{
		$customLocales = patForms::getStaticProperty('customLocales');
		if (!isset($customLocales[$locale])) {
			return false;
		}
		if (!isset($customLocales[$locale][$key])) {
			return false;
		}
		return $customLocales[$locale][$key];
	}
	
   /**
	* parses a locale file
	*
	* @access	private
	* @param	string		filename
	* @return	array		locale information
	* @todo		add some file checks
	*/
	function parseLocaleFile($filename)
	{
		return parse_ini_file($filename, true);
	}
	
   /**
	* sets the format of the element - this will be passed on to any elements you create. If you
	* have already added some elements when you call this method, it will be passed on to them too.
	*
	* @access	public
	* @param	string	$format	The name of the format you have implemented in your element(s).
	* @return	bool	$result	True on success
	* @see		setMode()
	* @see		format
	* @see		serialize()
	*/
	function setFormat( $format )
	{
		if( isset( $this ) && is_a( $this, 'patForms' ) )
		{
			$this->format	=	strtolower( $format );
			
			if( !empty( $this->elements ) )
			{
				$cnt	=	count( $this->elements );
				for( $i=0; $i < $cnt; $i++ )
				{
					$this->elements[$i]->setFormat( $format );
				}
			}
		}
		else
		{
			patForms::setStaticProperty( 'format', $format );
		}
		
		return true;
	}
	
   /**
	* sets the mode of the form - If you have already added some elements when you call this 
	* method, it will be passed on to them too.
	*
	* @access	public
	* @param	string	$mode	The mode to set the form to: default|readonly or any other mode you have implemented in your element class(es). Default is 'default'.
	* @see		setMode()
	* @see		mode
	* @see		serialize()
	*/
	function setMode( $mode )
	{
		$this->mode	=	strtolower( $mode );

		if( !empty( $this->elements ) )
		{
			$cnt	=	count( $this->elements );
			for( $i=0; $i < $cnt; $i++ )
			{
				$this->elements[$i]->setMode( $mode );
			}
		}
	}

   /**
	* sets the current submitted state of the form. Set this to true if you want the form
	* to pick up its submitted data. It will pass on this information to all elements that 
	* have been added so far, and new ones inherit it too.
	*
	* @access	public
	* @param	bool	$state	True if it has been submitted, false otherwise (default).
	* @see		isSubmitted()
	* @see		submitted
	*/
	function setSubmitted( $state )
	{
		if( $state == true )
		{
			$eventState	=	$this->triggerEvent( 'Submit' );
			if( $eventState === false )
				return	false;
		}

		$this->submitted	=	$state;

		if( !empty( $this->elements ) )
		{
			$cnt	=	count( $this->elements );
			for( $i=0; $i < $cnt; $i++ )
			{
				$this->elements[$i]->setSubmitted( $state );
			}
		}
		
		return $state;
	}

   /**
	* sets the method for the request
	*
	* @access	public
	* @param	string	$method		GET or POST
	* @see		method
	* @uses		setAttribute()
	*/
	function setMethod( $method )
	{
		$method	=	strtolower( $method );

		if( $method != 'get' && $method != 'post' )
		{
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_INVALID_METHOD, 
				'Unknown method "'.$method.'". Currently only GET and POST are supported as patForms methods.'
			);
		}
		$this->setAttribute( 'method', $method );
		return	true;
	}
	
   /**
	* sets the action for the form
	*
	* This is a only a wrapper for setAttribute()
	*
	* @access	public
	* @param	string	$action
	* @see		setAttribute()
	*/
	function setAction( $action )
	{
		return $this->setAttribute( 'action', $action );
	}

   /**
	* Sets the AutoFinalize mode for the form. The AutoFinalize mode will tell patForms to
	* finalize all elements after the form has been validated successfully.
	*
	* @access	public
	* @param	boolean	$mode		Whether to activate the AutoFinalize mode (true) or not (false).
	* @return	boolean	$success	True if okay, a patError object otherwise.
	* @see		finalizeForm()
	*/
	function setAutoFinalize( $mode )
	{
		if( !is_bool( $mode ) )
		{
			return patErrorManager::raiseError(
				PATFORMS_ERROR_PARAMETER_NO_BOOL,
				'The setAutoFinalize() method requires a boolean value ( true or false ) as parameter.'
			);
		}
	
		if( isset( $this ) && is_a( $this, 'patForms' ) )
		{
			$this->autoFinalize	=	$mode;
		}
		else
		{
			patForms::setStaticProperty( 'autoFinalize', $mode );
		}

		return true;
	}
	
   /**
	* Wrapper method that adds a filter to all elements
	* of the form at once instead of having to do it for
	* each element.
	*
	* @access	public
	* @param	object	&$filter	The filter object to apply
	* @see		patForms_Element::applyFilter()
	* @todo		add error management and docs once the element's applyFilter method has too
	*/
	function applyFilter( &$filter )
	{
		if( empty( $this->elements ) )
			return true;
			
		$cnt = count( $this->elements );

		for( $i = 0; $i < $cnt; $i++ )
		{
			$this->elements[$i]->applyFilter( $filter );
		}
	}
	
   /**
	* creates a new patForms object and returns it; this method is made to be called statically
	* to be able to create a new patForms object from anywhere.
	*
	* @access	public
	* @param	array	$formDefinition		Optional form definition for elements that will be added to the form
	* @param	array	$attributes			The attributes to set for the form itself
	* @return	object patForms	$form	The new patForms object.
	* @todo		it should be possible to pass Rule definitions, so they can be loaded and added	automatically.
	*/
	function &createForm( $formDefinition = null, $attributes = null )
	{
		$form	=	&new patForms();
		
		if( $attributes != null )
		{
			$form->setAttributes( $attributes );
		}
		
		if( $formDefinition === null )
			return	$form;

		foreach( $formDefinition as $name => $element )
		{
			if( !isset( $element["filters"] ) )
			{
				$element["filters"]	=	null;
			}
			if( !isset( $element["children"] ) )
			{
				$element["children"]	=	null;
			}
			
			$el	= &$form->createElement( $name, $element["type"], $element["attributes"], $element["filters"], $element["children"] );
			
			if( isset( $element["renderer"] ) ) {
				$el->setRenderer( $element["renderer"] );
			}
			
			$result		=	$form->addElement( $el );
			if (patErrorManager::isError( $result )) {
				return	$result;
			}
		}
		return $form;
	}

   /**
	* add a custom validation rule
	*
	* @access	public
	* @param	object patForms_Rule	validation rule
	* @param	integer					time to apply rule (before or after built-in validation)
	* @param	boolean					apply the rule, even if the form is invalid
	* @param	boolean					should form get revalidated (not implemented yet)
	* @return	boolean					currently always true
	*/
	function addRule( &$rule, $time = PATFORMS_RULE_AFTER_VALIDATION, $invalid = false, $revalidate = false )
	{
		$rule->prepareRule( $this );

		$this->_rules[]	=	 array(
									'rule'			=>	&$rule,
									'time'			=>	$time,
									'invalid'		=>	$invalid,
									'revalidate'	=>	$revalidate
								 );
	}
	
   /**
	* patForms PHP5 constructor - processes some intitialization tasks like merging the currently
	* set static properties with the internal properties.
	*
	* @access	public
	*/
	function __construct()
	{
		foreach( $this->staticProperties as $staticProperty => $setMethod )
		{
			$propValue	=	patForms::getStaticProperty( $staticProperty );
			if( patErrorManager::isError( $propValue ) )
				continue;
			
			$this->$setMethod( $propValue );
		}
		
		// initialize patForms internal attribute collection
		$this->loadAttributeDefaults();
	}
	
   /**
	* patForms pre-PHP5 constructor - does nothing for the moment except being a wrapper
	* for the PHP5 contructor for older PHP versions support.
	*
	* @access	public
	*/
	function patForms()
	{
		patForms::__construct();
	}

   /**
    * sets a renderer object that will be used to render
	* the form.
	*
	* @access	public
	* @param	object		&$renderer	The renderer object
	* @return	mixed		$success	True on success, patError object otherwise.
	* @see		createRenderer()
	* @see		renderForm()
	*/
	function setRenderer( &$renderer, $args = array() )
	{
		if( !is_object( $renderer ) )
		{
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_INVALID_RENDERER, 
				'You can only set a patForms_Renderer object with the setRenderer() method, "'.gettype( $renderer ).'" given.'
			);
		}

		$this->renderer	=	&$renderer;
		
		if( isset( $args['includeElements'] ) && $args['includeElements'] === true )
		{
			// check all elements - there may be some that need
			// renderers too, so we give them the same renderer if
			// they don't already have one.
			$cnt = count( $this->elements );
			for( $i = 0; $i < $cnt; $i++ )
			{
				if( $this->elements[$i]->usesRenderer && !is_object( $this->elements[$i]->renderer ) )
				{
					$this->elements[$i]->setRenderer( $renderer );
				}
			}
		}
		
		return true;
	}
	
   /**
    * sets a storage container object that will be used to store data
	*
	* @access	public
	* @param	object patForms_Storage
	* @see		createStorage()
	*/
	function setStorage( &$storage )
	{
		if( !is_object( $storage ) )
		{
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_INVALID_STORAGE, 
				'You can only set a patForms_Storage object with the setStorage() method, "'.gettype( $storage ).'" given.'
			);
		}

		$this->registerEventHandlerObject( $storage,
											array(
													'onInit'	=>	'loadEntry',
													'onSuccess'	=>	'storeEntry'
												)
										);
	}

   /**
	* renders the form with the renderer that was set via the {@link setRenderer()}
	* method.
	*
	* WARNING: This is still in alpha state!
	*
	* Should this method return a reference??
	* The return value could contain large blocks of HTML or large arrays!
	* Do we want to copy these?
	*
	* @access	public
	* @param	mixed	$args		arguments that will be passed to the renderer
	* @return	mixed	$form		The rendered form, or false if failed.
	*/
	function renderForm( $args = null )
	{
		if( $this->renderer === null )
		{
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_NO_RENDERER_SET, 
				'Form cannot be rendered, you have to set a renderer first via the setRenderer() method.'
			);
		}

		// form is not submitted, or auto-validation is disabled => render it
		if( !$this->isSubmitted() || $this->autoValidate !== true )
		{
			$this->triggerEvent( 'Init' );
			return $this->renderer->render( $this, $args );
		}

		$this->validateForm();

		return $this->renderer->render( $this, $args );
	}
	
   /**
	* Validates all elements of the form.
	*
	* @access	public
    * @param    boolean     Flag to indicate, whether form should be validated again, if it already has been validated.
	* @return	boolean	    True if all elements could be validated, false otherwise.
	* @see		finishForm()
	*/
	function validateForm( $revalidate = false )
	{
		if( $this->validated && !$revalidate )
			return $this->valid;

		$valid	=	true;
	
		/**
		 * validate custom rules
		 */
		if( !$this->_applyRules( PATFORMS_RULE_BEFORE_VALIDATION ) )
		{
			$valid	=	false;
		}

		/**
		 * validate elements
		 */
		if( $valid === true )
		{
			$cnt	=	count( $this->elements );
			for( $i = 0; $i < $cnt; ++$i )
			{
				if( !$this->elements[$i]->validate() )
				{
					$valid	=	false;
				}
			}
		}

		/**
		 * validate custom rules
		 */
		if( !$this->_applyRules( PATFORMS_RULE_AFTER_VALIDATION, $valid ) )
		{
			$valid	=	false;
		}
		
		if( $valid === true && $this->autoFinalize === true )
			$this->finalizeForm();
		
		$this->valid	=	$valid;
		
		$this->validated = true;

		if( $valid === true )
		{
			$this->_announce( 'status', 'validated' );
			$event	=	'Success';
		}
		else
		{
			$this->_announce( 'status', 'error' );
			$event	=	'Error';
		}

		$this->triggerEvent( $event );
		
		return $this->valid;
	}

   /**
	* apply rules
	*
	* @access	private
	* @param	integer		time of validation
	* @param	boolean		form is valid
	* @return	boolean		rules are valid or not
	* @todo		add documentation
	*/
	function _applyRules( $time, $isValid = true )
	{
		$valid = true;
		
		$cnt = count( $this->_rules );
		for ($i = 0; $i < $cnt; $i++) {
			
			// wrong time
			if (( $this->_rules[$i]['time'] & $time ) != $time) {
				continue;
			}
			if (!$isValid && !$this->_rules[$i]['invalid']) {
				continue;
			}

			$result	=	$this->_rules[$i]['rule']->applyRule( $this, PATFORMS_RULE_AFTER_VALIDATION );
			if( $result === false ) {
				$valid	=	false;
			}
		}
		return	$valid;
	}
	
   /**
	* Finalizes the form by telling each fom element to finalize - finalizing means to
	* process any tasks that need to be done after the form has been validated, like
	* deleting any temporary files or whatever an element needs to do at that point.
	*
	* @access	public
	* @return	bool	$success	Wether all elements could be finalized
	* @see		validateForm()
	*/
	function finalizeForm()
	{
		$success	=	true;
	
		$cnt	=	count( $this->elements );
		for( $i = 0; $i < $cnt; ++$i )
		{
			if( !$this->elements[$i]->finalize() )
			{
				patErrorManager::raiseWarning(
					PATFORMS_ERROR_ELEMENT_NOT_FINALIZED,
					'Element "'.$this->elements[$i]->elementName.'" could not be finalized. See the element error messages for more details.'
				);
				
				$success	=	false;
			}
		}
		
		return $success;
	}
	
   /**
	* creates a new renderer from the patForms renderer collection and returns it.
	*
	* @access	public
	* @param	string						The name of the renderer to create - have a look at the Renderer/ subfolder for a list of available renderers.
	* @return	object patForms_Renderer	The renderer object, or error object
	*/
	function &createRenderer( $name )
	{
		return	patForms::_createModule( 'Renderer', $name );
	}

   /**
	* creates a new storage container and returns it.
	*
	* @access	public
	* @param	string						The name of the storage to create - have a look at the Storage/ subfolder for a list of available storage containers.
	* @return	object patForms_Storage		The storage container, or error object
	*/
	function &createStorage( $name )
	{
		return	patForms::_createModule( 'Storage', $name );
	}

   /**
	* Creates a new filter and returns it.
	*
	* You may pass an array as second parameter that contains
	* parameters for the filter. patForms will check for setter methods
	* for all keys and set the corresponding values.
	*
	* This eases the creating of simple filter objects.
	*
	* @access	public
	* @param	string						The name of the filter to create - have a look at the Filter/ subfolder for a list of available filters.
	* @param	array						Optional parameters for the filter, if you provide a parameter, make sure the filter implements a set[Paramname]() method.
	*										This will be automated with interceptors in the PHP5 version of patForms
	* @return	object patForms_Filter		The filter, or error object
	*/
	function &createFilter( $name, $params = null )
	{
		$filter	=	&patForms::_createModule( 'Filter', $name );
		
		if( !is_array( $params ) )
		{
			return	$filter;
		}
		
		foreach( $params as $param => $value )
		{
			$setter		=	'set' . ucfirst( $param );
			if( method_exists( $filter, $setter ) )
			{
				$filter->$setter( $value );
			}
		}
		return	$filter;
	}

   /**
	* creates a new rule from the patForms rule collection and returns it.
	*
	* If your rules are not located in patForms/Rule you have to load and
	* instantiate them on your own.
	*
	* @access	public
	* @param	string					The name of the rule to create - have a look at the Rule/ subfolder for a list of available rules.
	* @param	string					The id of the rule, needed if the rule uses client side actions.
	* @return	object patForms_Rule	The rule object, or error object
	*/
	static function &createRule( $name, $id = null )
	{
		$rule	=	&patForms::_createModule( 'Rule', $name );
		if( $id != null )
		{
			$rule->setId( $id );
		}
		return $rule;
	}

   /**
	* creates a new observer from the patForms observer collection and returns it.
	*
	* If your observers are not located in patForms/Observer you have to load and
	* instantiate them on your own.
	*
	* @access	public
	* @param	string						The name of the observer to create - have a look at the Observer/ subfolder for a list of available observers.
	* @return	object patForms_Observer	The observer object, or error object
	*/
	function &createObserver( $name )
	{
		$observer = &patForms::_createModule( 'Observer', $name );

		return $observer;
	}

   /**
	* creates a new module for patForms
	*
	* @access	private
	* @param	string	$type		type of the module. Possible values are 'Renderer', 'Rule'
	* @param	string	$name		The name of the renderer to create - have a look at the renderers/ subfolder for a list of available renderers.
	* @return	object	$module		The module object, or an error object
	*/
	function &_createModule( $type, $name )
	{
		$baseFile		=	PATFORMS_INCLUDE_PATH . '/'.$type.'.php';
		$baseClass	=	'patForms_'.$type;

		// if there is an underscore in the module name, we want
		// to load the module from a subfolder, so we transform
		// all underscores to slashes.
		$pathName	=	$name;
		if( strstr( $pathName, '_' ) )
		{
			$pathName	=	str_replace( '_', '/', $name );
		}
		
		$moduleFile		=	PATFORMS_INCLUDE_PATH . '/'.$type.'/'.$pathName.'.php';
		$moduleClass	=	'patForms_'.$type.'_'.$name;
        if( !class_exists( $baseClass, FALSE ) )
		{
			if( !file_exists( $baseFile ) )
			{
				return patErrorManager::raiseError( 
					PATFORMS_ERROR_NO_MODULE_BASE_FILE, 
					$type .' base file could not be found',
					'Tried to load base file in path "'.$baseFile.'"' 
				);
			}
			
			include_once $baseFile;
		}
		
		if( !class_exists( $moduleClass, true ) )
		{
			if( !file_exists( $moduleFile ) )
			{
				if (defined('PATFORMS_LOCAL_INCLUDE_PATH')) {
                    $localModuleClass = PATFORMS_LOCAL_INCLUDE_PATH."/".$type."/".$pathName.".php";   
                    if (file_exists($localModuleClass)) {
                        include_once $localModuleClass;    
                        $module = new $moduleClass();
                        return $module;
                    }
                }
                
                return patErrorManager::raiseError( 
					PATFORMS_ERROR_MODULE_NOT_FOUND, 
					$type.' "'.$name.'" file "'.$moduleFile. '" could not be found.'
				);
			}
			include_once $moduleFile;
		}

		$module	= new $moduleClass();
		
		return $module;
	}

   /**
	* adds an element to the form - has to be a patForms_Element object. Use the {@link createElement()}
	* method to create a new element object. Also takes care of passing on the form's configuration
	* including the mode, format and submitted flags to the element.
	*
	* @access	public
	* @param	object	&$element	The patForms_Element object to add to this form.
	* @return	bool	$success	True if everything went well, false otherwise.
	* @see		patForms_Element
	* @see		createElement()
	*/
	function addElement( &$element )
	{
		if( !is_object( $element ) )
		{
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_ELEMENT_IS_NO_OBJECT, 
				'The addElement() method expects an element object, "'.gettype( $element ).'" given.'
			);
		}
		
		if( patErrorManager::isError( $element ) )
		{
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_UNEXPECTED_ERROR, 
				'The element you are trying to add is a patError object, and not a patForms element object.'
			);
		}

		if( !$element->getId() ) {
			$element->setId( $this->getElementId() );
		}
		$element->setMode( $this->getMode() );
		$element->setFormat( $this->getFormat() );
		$element->setSubmitted( $this->isSubmitted() );
		$element->setLocale( $this->getLocale() );

		$this->elements[]	=&	$element;		
		
		return true;
	}

   /**
	* replaces an element in the form
	*
	* @access	public
	* @param	object	$element	The patForms_Element object to be replaced
	* @param	object	&$replace	The element that will replace the old element
	* @return	bool	$success	True if everything went well, false otherwise.
	* @see		patForms_Element
	* @see		addElement()
	*/
	function replaceElement( $element, &$replace )
	{
		if( !is_object( $replace ) ) {
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_ELEMENT_IS_NO_OBJECT, 
				'The addElement() method expects an element object, "'.gettype( $replace ).'" given.'
			);
		}
		
		if( patErrorManager::isError( $replace ) ) {
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_UNEXPECTED_ERROR, 
				'The element you are trying to add is a patError object, and not a patForms element object.'
			);
		}
		
		if (is_object($element)) {
			$element = $element->getId();
		}
		
		$cnt = count($this->elements);
		for ($i = 0; $i < $cnt; $i++) {
			if ($this->elements[$i]->getId() === $element) {

				if( !$replace->getId() ) {
					$replace->setId( $this->getElementId() );
				}
				$replace->setMode( $this->getMode() );
				$replace->setFormat( $this->getFormat() );
				$replace->setSubmitted( $this->isSubmitted() );
				$replace->setLocale( $this->getLocale() );
	
				$this->elements[$i] = &$replace;
				return true;
			}
			
			// the current element is a container
			if (method_exists($this->elements[$i], 'replaceElement')) {
				$result = $this->elements[$i]->replaceElement($element, $replace);
				if ($result === true) {
					return $result;
				}
			}
		}
		
		return false;
	}

   /**
	* Get an element by its name.
	*
	* @access	public
	* @param	string	$name	name of the element
	* @return	object			patForms element
	* @deprecated	please use patForms::getElementByName() instead
	*/
	function &getElement( $name )
	{
		return $this->getElementByName( $name );
	}

   /**
	* Get an element by its name.
	*
	* @access	public
	* @param	string	$name	name of the element
	* @return	mixed			either a patForms element or an array containing patForms elements
	* @see		getElementById()
	*/
	function &getElementByName( $name )
	{
		if( $name == '__form' ) {
			return $this;
		}
		
		$elements = array();
		$cnt      = count( $this->elements );
		for ($i = 0; $i < $cnt; $i++) {
			if ($this->elements[$i]->getName() == $name) {
				$elements[]	= &$this->elements[$i];
				continue;
			}
			if (method_exists($this->elements[$i], 'getElementById')) {
				patErrorManager::pushExpect(PATFORMS_ERROR_ELEMENT_NOT_FOUND);
				$result = &$this->elements[$i]->getElementByName($name);
				patErrorManager::popExpect();
				if (!patErrorManager::isError($result)) {
					if (is_array($result)) {
						$cnt2 = count( $result );
						for ($j = 0; $j < $cnt2; $j++) {
							$elements[]	= &$result[$j];
						}
					} else {
						$elements[]	= &$result;
					}
				}
			}
		}
		
		switch( count( $elements ) )
		{
			case	0:
                $r = patErrorManager::raiseError( 
					PATFORMS_ERROR_ELEMENT_NOT_FOUND, 
					'Element '.$name.' could not be found.'
				);
				return $r; 
				break;
			case	1:
				return	$elements[0];
				break;
			default:
				return	$elements;
				break;
		}
	}

   /**
	* Get an element by its id.
	*
	* @access	public
	* @param	string	$id		id of the element
	* @return	object			patForms element
	*/
	function &getElementById( $id )
	{
		$cnt	=	count( $this->elements );
		for( $i = 0; $i < $cnt; $i++ )
		{
			if( $this->elements[$i]->getId() == $id ) {
				return $this->elements[$i];
			}
			if (method_exists($this->elements[$i], 'getElementById')) {
				patErrorManager::pushExpect(PATFORMS_ERROR_ELEMENT_NOT_FOUND);
				$result = &$this->elements[$i]->getElementById($id);
				patErrorManager::popExpect();
				if (!patErrorManager::isError($result)) {
					return $result;
				}
			}
		}
		return patErrorManager::raiseError( 
			PATFORMS_ERROR_ELEMENT_NOT_FOUND, 
			'Element '.$name.' could not be found.'
		);
	}

   /**
	* Get all elements of the form
	*
	* @access	public
	* @return	array	all elements of the form
	*/
	function &getElements()
	{
		return	$this->elements;
	}

   /**
	* Creates a new form element and returns a reference to it.
	*
	* The optional $filters array has to be in the following format:
	*
	* <pre>
	* array(
	*       array(
	*              'filter' => 'Multiplier',
	*              'params' => array( 'multiplier' => 6 )
	*            )
	*	   )
	* </pre>
	*
	* @access	public
	* @param	string	$name		The name of the element
	* @param	string	$type		The type of the element; for a list of possible elements, have a look at the elements/ subfolder of the patForms package.
	* @param	array	$attributes	Attributes for the element
	* @param	array	$filters	Optional filters that will be applied
	* @return	object patForms_Element	$element	The element object, or patError if failed.
	*/
	function &createElement( $name, $type, $attributes, $filters = null, $children = null )
	{
		$element =& patForms::_createModule( 'Element', $type );
		if( patErrorManager::isError( $element ) )
		{
			return	$element;
		}

		$attributes['name']	=	$name;
		if( !isset( $attributes['id'] ) ) {
			$attributes['id'] = patForms::getElementId();
		}
		
		// add default attributes - do this the 'silent' way be checking whether
		// the element supports the given attribute, as the element throws a notice
		// if it does not support it - this is not expected from default attributes.
		foreach( patForms::getStaticProperty( 'defaultAttributes' ) as $attributeName => $attributeValue ) 
		{
			if( !$element->hasAttribute( $attributeName ) )
			{
				continue;
			}
			
			$element->setAttribute( $attributeName, $attributeValue );
		}
	
		// set the given attributes normally
		$success = $element->setAttributes( $attributes );
		if( patErrorManager::isError( $success ) )
		{
			return $success;
		}

		if (is_array($children)) {
			foreach ($children as $child) {
				$childName = $child['attributes']['name'];
				
				$childEl = &patForms::createElement($childName, $child['type'], $child['attributes']);
				if( isset( $child["renderer"] ) ) {
					$childEl->setRenderer( $child["renderer"] );
				}
				
				$element->addElement($childEl);
			}
		}
		
		$success = $element->_init();
		if( patErrorManager::isError( $success ) ) {
			return $success;
		}
		
		// if we don't have any filters to add, we're done
		if( !is_array( $filters ) )
		{
			return $element;
		}
		
		$cnt	=	count( $filters );
		for( $i = 0; $i < $cnt; $i++ )
		{
			$params =	isset( $filters[$i]['params'] ) ? $filters[$i]['params'] : null;
			$filter	=	&patForms::createFilter( $filters[$i]['filter'], $params );
			if( patErrorManager::isError( $filter ) )
			{
				continue;
			}
			$element->applyFilter( $filter );
		}
		
		return $element;
	}
	
   /**
	* retrieves the validation errors from all elements in the form. Use this if the validateForm()
	* method returned false.
	*
	* @access	public
	* q
	* @return	array	$errors	Array containing an array with validation errors for each element in the form.
	* @todo		replace	__form with the name of the form, once attributes are implemented
	*/
	function getValidationErrors($withElements = true)
	{
		$found	=	false;
		$errors	=	array();
		
		if( !empty( $this->validationErrors ) )
		{
			$errors['__form']	=	$this->validationErrors;
			$found = true;
		}
		
		if ($withElements === false) {
			return $errors;
		}
		
		$cnt = count( $this->elements );
		for( $i = 0; $i < $cnt; ++$i )
		{
			$name	=	$this->elements[$i]->getAttribute( 'name' );
			if( $name === false )
			{
				continue;
			}
			
			$elementErrors = $this->elements[$i]->getValidationErrors();
			
			if( empty( $elementErrors ) )
				continue;
				
			$errors[$name]	=	$elementErrors;
			$found = true;
		}
		
		if( $found )
			return $errors;
			
		return false;
	}
	
   /**
	* retrieves the values for all elements in the form.
	*
	* @access	public
	* @param	array		desired fields
	* @param	integer		Mode that should be used to return values in groups
	* @return	array		The values for all elements, as elementname => elementvalue.
	*
	* @todo		remove the ugly Group check and replace with something better
	* @todo		implement something similar for getValidation errors
	*/
	function getValues( $fields = null, $type = PATFORMS_VALUES_NESTED )
	{
		$values	=	array();
		
		$cnt = count( $this->elements );
		for( $i = 0; $i < $cnt; ++$i )
		{
			$name	=	$this->elements[$i]->getAttribute( 'name' );
			if( $name === false ) {
				continue;
			}
			
			if( is_array( $fields ) && !in_array( $name, $fields ) ) {
				continue;
			}

			$tmpVal = $this->elements[$i]->getValue();
			if (!is_array($tmpVal) || $this->elements[$i]->elementName != 'Group') {
				$values[$name] = $tmpVal;
				continue;			
			}
			
			switch ($type) {
				case PATFORMS_VALUES_FLATTENED:
					$values = array_merge($values, $tmpVal);
					break;
				case PATFORMS_VALUES_PREFIXED:
					foreach ($tmpVal as $key => $val) {
						$values[$name.'_'.$key] = $val;
					}
					break;
				case PATFORMS_VALUES_NESTED:
				default:
					$values[$name] = $tmpVal;
					break;

			}
		}
		return $values;
	}
	
   /**
	* sets the values for all elements in the form. Use this to fill your form with external
	* data, like a db query. Caution: if you do this and set the form to submitted, the values
	* will be overwritten by any values present in the $_GET or $_POST variables.
	*
	* @access	public
	* @param	array	$values	The values for all elements, as elementname => elementvalue.
	*/
	function setValues( $values, $overrideUserInput = false )
	{
		patErrorManager::pushExpect(PATFORMS_ERROR_ELEMENT_NOT_FOUND);
        if(is_array($values)) {
            foreach ($values as $elName => $value) {
                $el = &$this->getElementByName($elName);
                if (patErrorManager::isError($el)) {
                    continue;
                }
                if ($overrideUserInput === true) {
                    $el->setValue($value);
                } else {
                    $el->setDefaultValue($value);
                }
            }
        }
		patErrorManager::popExpect();
		return true;
	}
	
   /**
	* retrieves the current mode of the form
	*
	* @access	public
	* @return	string	$mode	The current form mode
	* @see		setMode()
	* @see		$mode
	*/
	function getMode()
	{
		return $this->mode;
	}
	
   /**
	* 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;
	}
	
   /**
	* retrieves the current format of the form
	*
	* @access	public
	* @return	string	$format	The current form format
	* @see		setFormat()
	* @see		format
	*/
	function getFormat()
	{
		return $this->format;
	}
	
   /**
	* retrieves the current method of the form
	*
	* @access	public
	* @return	string	$method	The request method
	* @see		setMethod()
	*/
	function getMethod()
	{
		return $this->getAttribute( 'method' );
	}
	
   /**
	* retrieves the current action of the form
	*
	* @access	public
	* @return	string	$action		Action of the form
	* @see		setAction()
	*/
	function getAction()
	{
		$action = $this->getAttribute( 'action' );
		if( !empty( $action ) )
			return $action;
		return	$_SERVER['PHP_SELF'];
	}
	
   /**
	* adds an atribute to the form's attribute collection. If the attribute
	* already exists, it is overwritten.
	*
	* @access	public
	* @param	string	$attributeName	The name of the attribute to add
	* @param	string	$atributeValue	The value of the attribute
	*/
	function setAttribute( $attributeName, $attributeValue )
	{
		if( !isset( $this->attributeDefinition[$attributeName] ) )
		{
			patErrorManager::raiseNotice( 
				PATFORMS_NOTICE_ATTRIBUTE_NOT_SUPPORTED, 
				"The attribute '".$attributeName."' is not supported by the form, skipped it. [".get_class( $this )."]" 
			);
			return true;
		}
		
		$this->attributes[$attributeName]	=	$attributeValue;
		
		return true;
	}
	
   /**
	* adds several attributes at once to the form's attribute collection.
	* Any existing attributes will be overwritten.
	*
	* @access	public
	* @param	array	$attributes	The attributes to add
	* @see		setAttribute()
	*/
	function setAttributes( $attributes )
	{
		if( !is_array( $attributes ) )
		{
			return patErrorManager::raiseError( 
				PATFORMS_NOTICE_ARRAY_EXPECTED, 
				"setAttributes: array expected"
			);
		}
	
		foreach( $attributes as $attributeName => $attributeValue )
		{
			$this->setAttribute( $attributeName, $attributeValue );
		}
		
		return true;
	}

   /**
	* retrieves the value of a form 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 of the form, 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).
	*
	* The action defaults to the path of the current script, with session
	* ID appended automatically, if SID has been defined.
	*
	* @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'];
			}
			
			if( $attributeName == 'action' )
			{
				$this->attributes[$attributeName]	=	$_SERVER['PHP_SELF'];
				/**
				 * session has been started, append session ID
				 */
				if( defined( 'SID' ) )
					$this->attributes[$attributeName] .= '?' . SID;
			}
		}
		
		return true;
	}

   /**
	* retrieves the form's current submitted state.
	*
	* If autoValidate is used, it will check for the submitVar and
	* set the submitted flag accordingly
	*
	* @access	public
	* @return	bool	$state	True if it has been submitted, false otherwise.
	* @see		setSubmitted(), setAutoValidate()
	* @see		submitted
	*/
	function isSubmitted()
	{
		if( $this->submitted === true )
		{
			return true;
		}
		
		if( !isset( $this->submitVar ) )
		{
			return	false;
		}

		if( !$this->autoValidate )
		{
			return	false;
		} 

		if( isset( $_GET[$this->submitVar] ) || isset( $_POST[$this->submitVar] ) )
		{
			$this->setSubmitted( true );
		}
		
		return $this->submitted;
	}
	
   /**
	* Creates a new patForms_Creator object
	*
	* @static
	* @access	public
	* @return	object	$creator	The creator object, or a patError object on failure
	*/
	function createCreator( $type )
	{
		return patForms::_createModule( 'Creator', $type );
	}
	
   /**
	* get the element name of the form
	*
	* @access	public
	* @return	string	name of the form
	*/
	function getElementName()
	{
		return $this->elementName;
	}

   /**
	* 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;
			}
		}
	}

   /**
	* add a validation error to the whole form
	*
	* This can be achieved by adding a validation rule to the form.
	*
	* @access	public
	* @param	integer	$code
	* @param	array	$vars	fill named placeholder with values
	* @return 	boolean $result	true on success
	* @see		addRule()
	*/
    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_NOTICE_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_WARNING_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;
    }

   /**
	* Retrieves a new element id, used to give each added element a unique id for this
	* form (id can be overwritten by setting the id attribute specifically).
	*
	* @static
	* @access	public
	* @return	int	$elementId	The new element id.
	*/
	function getElementId()
	{
		$GLOBALS['_patForms']['elementCounter']++;
		return 'pfo'.$GLOBALS['_patForms']['elementCounter'];
	}

   /**
	* attach an observer
	*
	* @access	public
	* @param	object	patForms_Observer
	* @see		createObserver()
	* @uses		patForms_Element::createObserver()
	*/
	function attachObserver( &$observer, $where = PATFORMS_OBSERVER_ATTACH_TO_ELEMENTS )
	{
		/**
		 * attach the observer to all elements
		 */
		if( ( $where & PATFORMS_OBSERVER_ATTACH_TO_ELEMENTS ) == PATFORMS_OBSERVER_ATTACH_TO_ELEMENTS )
		{
			$cnt	=	count( $this->elements );
			for( $i = 0; $i < $cnt; ++$i )
			{
				$this->elements[$i]->attachObserver( $observer );
			}
		}

		/**
		 * attach the observer to the form
		 */
		if( ( $where & PATFORMS_OBSERVER_ATTACH_TO_FORM ) == PATFORMS_OBSERVER_ATTACH_TO_FORM )
		{
			$this->observers[] = &$observer;
		}
		return true;
	}

   /**
 	* Retrieve the content for the start of the form, including any 
	* additional content, e.g. global scripts if the scripts option 
	* is enabled.
	*
	* @access	public
	* @return	string	$formStart	The form start content
	* @todo		use format to build a dynamic method
	*/
	function serializeStart()
	{
		$methodName	=	"serializeStart".ucfirst( $this->getFormat() ).ucfirst( $this->getMode() );
		
		if( !method_exists( $this, $methodName ) )
		{
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_METHOD_FOR_MODE_NOT_AVAILABLE, 
				"Method for patForms mode '".$this->getMode()."' (".$methodName.") is not available." 
			);
		}
		
		return	$this->$methodName();
	}
	
   /**
	* Serializes the form's start element for html format, in default mode.
	*
	* @access	private
	* @return	mixed	$formStart	The serialized start content, or a patError object.
	*/
	function serializeStartHtmlDefault()
	{
		$attributes	= $this->getAttributesFor( $this->format );
		if( patErrorManager::isError( $attributes ) )
		{
			return $attributes;
		}

		$content = patForms_Element::createTag( 'form', 'opening', $attributes );
		
		if ($this->optionEnabled( 'scripts' )) {
			$content .= $this->getScripts();
		}
		
		return $content;
	}
	
   /**
	* Serializes the form's start element for html format, in readonly mode.
	*
	* @access	private
	* @return	mixed	$formStart	The serialized start content, or a patError object.
	*/
	function serializeStartHtmlReadonly()
	{
		$attributes	= $this->getAttributesFor( $this->format );
		if( patErrorManager::isError( $attributes ) )
		{
			return $attributes;
		}

		return patForms_Element::createTag( 'form', 'opening', $attributes );
	}

   /**
 	* Retrieve the content for the end of the form.
	*
	* @access	public
	* @return	string	$formEnd	The form end content
	*/
	function serializeEnd()
	{
		$methodName	=	"serializeEnd".ucfirst( $this->getFormat() ).ucfirst( $this->getMode() );
		
		if( !method_exists( $this, $methodName ) )
		{
			return patErrorManager::raiseError( 
				PATFORMS_ERROR_METHOD_FOR_MODE_NOT_AVAILABLE, 
				"Method for patForms mode '".$this->getMode()."' (".$methodName.") is not available." 
			);
		}
		
		return	$this->$methodName();
	}
	
   /**
	* Retrieves the content for the end of the form for html format,
	* in default mode.
	*
	* @access	private
	* @return	string	$formEnd	The form end content	
	*/
	function serializeEndHtmlDefault()
	{
		return	patForms_Element::createTag( 'form', 'closing' );
	}

   /**
	* Retrieves the content for the end of the form for html format,
	* in readonly mode.
	*
	* @access	private
	* @return	string	$formEnd	The form end content	
	*/
	function serializeEndHtmlReadonly()
	{
		return	$this->serializeEndHtmlDefault();
	}

   /**
	* 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_ERROR_ATTRIBUTE_REQUIRED, 
						'patForms needs the attribute "'.$attributeName.'" to be set.',
						'See the patForms attribute definition of patForms for a complete attribute reference.'
					);
				}
				
				continue;
			}
			
			$attributeValue	=	$this->attributes[$attributeName];
			
			if( !in_array( $format, $attributeDef["outputFormats"] ) )
			{
				continue;
			}
			
			if( isset( $attributeDef["format"] ) )
			{
				if( !$this->_checkAttributeFormat( $attributeValue, $attributeDef["format"] ) )
				{
					return patErrorManager::raiseError( 
						PATFORMS_ERROR_CAN_NOT_VERIFY_FORMAT, 
						"Format '".$attributeDef["format"]."' could not be verified for patForms attribute '".$attributeName."' => '".$attributeValue."'" 
					);
				}
			}
			
			$attributes[$attributeName]	=	$attributeValue;
		}
		
		return $attributes;
	}
	
   /**
	* 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;
	}

   /**
	* Enables a patForms option.
	* 
	* See the {@link $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
	* @return	mixed	$result		True on success, patError object otherwise.
	* @see		disableOption()
	* @see		optionEnabled()
	* @see		$options
	*/
	function enableOption( $option, $params = array() )
	{
		if( !in_array( $option, array_keys( $this->options ) ) )
		{
			return patErrorManager::raiseNotice(
				PATFORMS_NOTICE_INVALID_OPTION,
				'The option "'.$option.'" is not a valid patForms option.'
			);
		}
		
		$this->options[$option]['enabled'] = true;
		$this->options[$option]['params']  = array_merge($this->options[$option]['params'], $params);
		
		// now update all available elements too
		$cnt = count( $this->elements );
		for( $i=0; $i < $cnt; $i++ )
		{
			$this->elements[$i]->enableOption( $option, $params );
		}
		
		return true;
	}
	
   /**
	* Disables a patForms option
	* 
	* See the {@link $options} property for an exhaustive list of available options.
	*
	* @access	public
	* @param	string	$option	The option to disable
	* @return	mixed	$result	True on success, patError object otherwise.
	* @see		enableOption()
	* @see		optionEnabled()
	* @see		$options
	*/
	function disableOption( $option )
	{
		if( !in_array( $option, array_keys( $this->options ) ) )
		{
			return patErrorManager::raiseNotice(
				PATFORMS_NOTICE_INVALID_OPTION,
				'The option "'.$option.'" is not a valid patForms option.'
			);
		}
		
		$this->options[$option]['enabled']	=	false;
		
		// now update all available elements too
		$cnt = count( $this->elements );
		for( $i=0; $i < $cnt; $i++ )
		{
			$this->elements[$i]->disableOption( $option );
		}

		return true;
	}
	
   /**
	* Checks whether the given option is enabled.
	*
	* @access	public
	* @param	string	$option		The option to check
	* @return	bool	$enabled	True if enabled, false otherwise.
	* @see		enableOption()
	* @see		disableOption()
	* @see		getOptionParameters()
	* @see		$options
	*/
	function optionEnabled( $option )
	{
		if( !isset( $this->options[$option] ) )
			return false;
			
		return $this->options[$option]['enabled'];
	}

   /**
	* returns the parameters that have been supplied for an option
	*
	* @access	public
	* @param	string	$option		The option to check
	* @return	array
	* @see		enableOption()
	* @see		disableOption()
	* @see		optionEnabled()
	* @see		$options
	*/
	function getOptionParameters( $option )
	{
		if( !isset( $this->options[$option] ) )
			return false;

		return $this->options[$option]['params'];
	}

   /**
	* Set the form to auto validate
	*
	* If you use this method, patForms will check the _GET and _POST variables
	* for the variable you specified. If it is set, patForms assumes, that
	* the form has been submitted.
	*
	* When creating a start tag for the form, the value will be inserted automatically.
	*
	* @access	public
	* @param	string	$submitVar
	*/
	function setAutoValidate( $submitVar )
	{
		$this->autoValidate	=	true;
		$this->submitVar	=	$submitVar;
	}

   /**
	* register a new event
	*
	* After registering an event, you may register one or more
	* event handlers for this event an then trigger the event.
	*
	* This lets you extend the functionality of patForms.
	*
	* @access	public
	* @param	string	event name
	* @return	boolean	true, if event could be registered
	* @see		registerEventHandler()
	* @see		triggerEvent()
	*/
	function registerEvent( $name )
	{
		$event	=	'on' . $name;
		if( in_array( $event, $this->_validEvents ) )
		{
			return patErrorManager::raiseNotice(
												PATFORMS_NOTICE_EVENT_ALREADY_REGISTERED,
												'Event "'.$event.'" already has been registered or is built-in event'
												);
		}
		array_push( $this->_validEvents, $event );
		return true;
	}
	
   /**
	* Register an event handler
	*
	* An event handler can be any valid PHP callback. You may pass
	* one of the following values:
	* - string functionname to call a globally declared function 
	* - array( string classname, string methodname) to call a static method
	* - array( object obj, string methodname) to call a method of an object
	*
	* When the handler is called, two parameters will be passed:
	* - object form  : a patForms object
	* - string event : the name of the event has should be handled.
	*
	* An event handler should always return true. If false is returned,
	* the event will be cancelled.
	* 
	* Currently handlers for the following events can be registered:
	* - onSubmit
	* - onSuccess
	* - onError
	*
	* @access	public
	* @param	string	event name
	* @param	mixed	event handler
	* @return	boolean	true, if the handler could be registered
	* @see		triggerEvent()
	* @see		$_validEvents
	*/
	function registerEventHandler( $event, $handler )
	{
		if( !in_array( $event, $this->_validEvents ) )
		{
			return patErrorManager::raiseError(
												PATFORMS_ERROR_UNKNOWN_EVENT,
												'Cannot register event handler for unknown event "' . $event .'".'
												);
		}
		
		if( !is_callable( $handler ) )
		{
			return patErrorManager::raiseError(
												PATFORMS_ERROR_INVALID_HANDLER,
												'Event handler is not callable.'
												);
		}
		
		if( !isset( $this->_eventHandler[$event] ) )
		{
			$this->_eventHandler[$event]	=	array();
		}
		
		$this->_eventHandler[$event][]	=	&$handler;
		return true;
	}

   /**
	* set event handler object.
	*
	* An event handler object is used to handle all
	* registered events. The object has to provide methods
	* for all events it should handle, the names of the methods
	* have to be the same as the names of the events.
	*
	* @access	public
	* @param	object	event handler object
	* @param	array	method names, used to change the names of the methods
	* @return	boolean
	*/
	function registerEventHandlerObject( &$obj, $methods = array() )
	{
		if( empty( $methods ) )
		{
			foreach( $this->_validEvents as $event )
			{
				if( !method_exists( $obj, $event ) )
					continue;
				
				$methods[$event]	=	$event;
			}
		}

		foreach( $methods as $event => $method )
		{
			if( !isset( $this->_eventHandler[$event] ) )
			{
				$this->_eventHandler[$event]	=	array();
			}

			$this->_eventHandler[$event][]	=	array( &$obj, $method );
		}

		return	true;
	}
	
   /**
	* Trigger an event
	*
	* In most cases there's no need to call this event
	* from outside the class. The method is declared public
	* to allow you to trigger custom events.
	*
	* @access	public
	* @param	string	Event name. The event name must not contain 'on', as this will be
	*					prefixed automatically.
	*/
	function triggerEvent( $event )
	{
		$handlerName	=	'on' . $event;

		if( !isset( $this->_eventHandler[$handlerName] ) || empty( $this->_eventHandler[$handlerName] ) )
		{
			return true;
		}

		$cnt	=	count( $this->_eventHandler[$handlerName] );
		for( $i = 0; $i < $cnt; $i++ )
		{
			$result	=	call_user_func( $this->_eventHandler[$handlerName][$i], $this, $event );
			if( $result == false )
			{
				return $result;
			}
		}
		return true;
	}
	
   /**
	* Serializes the entire form to XML, all elements included
	* 
	* @access	public
	* @param	string	$namespace	Optional namespace to use for the tags
	* @return	string	$xml		The XML representation of the form
	* @see		patForms_Element::toXML()
	* @todo		needs patForms_Element, maybe switch to PEAR::XML_Util
	*/
	function toXML( $namespace = null )
	{
		$tagName = 'Form';

		// 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 );
		}
		
		$elements = '';
		for( $i = 0; $i < $this->elementCounter; $i++ )
		{
			$elements .= $this->elements[$i]->toXML( $namespace );
		}
		
		return	patForms_Element::createTag( $tagName, "full", $attributes, $elements );
	}
	
   /**
	* Set a static property.
	*
	* Static properties are stored in an array in a global variable,
	* until PHP5 is ready to use.
	*
	* @static
	* @param	string	property name
	* @param	mixed	property value
	* @see		getStaticProperty()
	*/
	function setStaticProperty( $property, &$value )
	{
		$GLOBALS["_patForms"][$property]	=	&$value;
	}

   /**
	* Get a static property.
	*
	* Static properties are stored in an array in a global variable,
	* until PHP5 is ready to use.
	*
	* @static
	* @param	string	property name
	* @return	mixed	property value
	* @see		setStaticProperty()
	*/
	function &getStaticProperty( $property )
	{
		if( isset( $GLOBALS["_patForms"][$property] ) )
		{
			return	$GLOBALS["_patForms"][$property];
		}
		return	patErrorManager::raiseWarning(
			PATFORMS_ERROR_NO_STATIC_PROPERTY, 
			'Static property "'.$property.'" could not be retreived, it does not exist.'
		);
	}
	
   /**
	* Retrieves the form's name
	*
	* If no name is set, it will use 'patForms' as name.
	*
	* @access	public
	* @return	string	$name	The name of the form.
	*/
	function getName()
	{
		if( isset( $this->attributes['name'] ) )
			return $this->attributes['name'];
		return 'patForms';
	}

   /**
	* get the javascript for the form
	*
	* This is still in alpha state. It will later
	* allow client side validation if the element
	* provides this feature.
	*
	* @access	public
	* @return	string	javascript needed by the form
	* @todo		make this dependent on the format
	* @todo		add changeable linebreaks
	* @todo		store included javascripts in some kind of static property
	*/	
	function getScripts()
	{
		$displayedTypes   = array();
		$globalJavascript = array();
		$instances = '';
		
		$cnt = count( $this->elements );
		for ($i = 0; $i < $cnt; ++$i) {
			$instances .= $this->elements[$i]->getInstanceJavascript();

			$type = $this->elements[$i]->getElementName();
			if (in_array($type, $displayedTypes)) {
				continue;
			}

			array_push( $displayedTypes, $type );
			$globalScript = $this->elements[$i]->getGlobalJavascript();
			if (!empty($globalScript)) {
				array_push($globalJavascript, $globalScript);
			}
		}
		
		$cnt = count( $this->_rules );
		for( $i = 0; $i < $cnt; ++$i )
		{
			$instances .= $this->_rules[$i]['rule']->getInstanceJavascript();

			$type = $this->_rules[$i]['rule']->getRuleName();
			if (in_array($type, $displayedTypes)) {
				continue;
			}

			array_push( $displayedTypes, $type );
			$globalScript = $this->_rules[$i]['rule']->getGlobalJavascript();
			if (!empty($globalScript)) {
				array_push($globalJavascript, $globalScript);
			}
		}

		$optionParams = $this->getOptionParameters('scripts');
		$scriptFolder = $optionParams['folder'];

		if ($optionParams['jsInclude'] === true) {
			$script = "\n";
			foreach ($globalJavascript as $scriptFile) {
				$fullPath = $scriptFolder . '/' . $scriptFile;
				if (!file_exists($fullPath)) {
					patErrorManager::raiseWarning(PATFORMS_WARNING_SCRIPTFILE_NOT_FOUND, 'Could not script file.', $fullPath);
					continue;
				}
				$script .= '<script type="text/javascript" language="Javascript1.3" src="'.$fullPath.'"></script>' . "\n";
			}
			$script .= '<script type="text/javascript" language="Javascript1.3">' . "\n";
			$script .= $instances;
			$script .= '</script>';
		} else {
			$script = "\n".'<script type="text/javascript" language="Javascript1.3">' . "\n";
			foreach ($globalJavascript as $scriptFile) {
				$fullPath = $scriptFolder . '/' . $scriptFile;
				if (!file_exists($fullPath)) {
					patErrorManager::raiseWarning(PATFORMS_WARNING_SCRIPTFILE_NOT_FOUND, 'Could not script file.', $fullPath);
					continue;
				}
				$script .= file_get_contents($fullPath);
			}
			$script .= "\n\n";
			$script .= $instances;
			$script .= "\n";
			$script .= '</script>';
		}
		
		return $script;
	}
	
   /**
	* 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;
	}
}
?>
