<?php
// Modification log
// (date)       (author)    (activity/purpose)
// 30.07.03     T.Gildseth  Added support for <select> elements
// 22.08.03     T.Gildseth  Added assertion checks
// 22.08.03     T.Gildseth  Added and updated comments
// 24.08.03     T.Gildseth  Removed strToLower on element name in method _findFormElements
//                          ***This will break old code which depend on incorrect behaviour***
// 25.08.03     T.Gildseth  Modified _findFormElements, so that also root node gets processed.
// 04.10.03     T.Gildseth  Added addOptGroup method, for adding option groups in select elements
// 21.11.03     T.Gildseth  A somewhat ugly bugfix in _handleSelectElem()
//
// Todo
// (date)       (author)        (activity/purpose)
// 22.08.03     Tommy Gildseth  Add support modifying <option> elements value

require_once 'classes/XML/class.XmlParser.php';
assert_options(ASSERT_WARNING0);

/**
 * This class gives easy access to modify and verify a form.
 *
 * @author Tommy Gildseth <tommy@akili.no>;
 * @author Akili AS <post@akili.no>;
 * @copyright Akili AS, July 2003
 * @version 0.5.1
 * @access public
 * @package XHTML
 */
class Form
    
{
    
//{{{ Class members
    /**
     * This is a reference to the root node of the XML tree
     * @access private
     */
    
var $_xmlTree;

    
/**
     * Elements in the form
     * @access private
     */
    
var $_elements;

    
/**
     * Mandatory fields in the form
     * @access private
     */
    
var $_reqFields;

    
/**
     * Wether the form was submitted using GET or POST
     * @access private
     */
    
var $_requestMethod;
    
//}}} End class member declarations

    //{{{Constructor
    /**
     * Form constructor
     *
     * @param string $form Either a string containing the form, or the filename of a
     *                     file which contains it.
     * @access public
     */
    
function Form$form )
        {
        
$xmlParser = new XmlParser();

        if (
strlen($form) < 100 && is_file($form))
            
$xmlParser->parseFile($form);
        else
            
$xmlParser->parseString($form);

        
$this->_xmlTree $xmlParser->getRootNode();
        
$this->_findFormElements($this->_xmlTree);
        }
//}}}end Constructor

    //{{{_findFormElements
    /**
     * Find all elements which belong to a form
     *
     * @param object XmlNode &$node XmlNode object containing the form element and
     *                             it's children
     * @access private
     */
    
function _findFormElements( &$node )
        {
        if (!
assert ('is_object($node) && is_a($node, "XmlNode")'))
            {
            
$errorMsg 'Assertion failed in call to Form::_findFormElements:<br/>';
            
$errorMsg .= 'The Xml tree contains an invalid tree or substree.<br/>';
            
$errorMsg .= 'Dump of tree passed to function:<br/><pre>';
            echo 
$errorMsg;
            
print_r($node);
            echo 
'</pre>';
            
$this->_printStackTrace();
            return;
            }

        
$name $node->getAttribute('name');
        
$elemType $node->getName();
        if (
$name !== null || $elemType == 'form')
            {
            switch (
$elemType)
                {
                case 
'input':
                    
$this->_handleInputElem($node);
                    break;
                case 
'select':
                    
$this->_handleSelectElem($node);
                    
$this->_elements[$name]['selectElem'] = &$node;
                    break;
                case 
'textarea':
                    
$this->_elements[$name] = &$node;
                    break;
                case 
'form':
                    
$this->_requestMethod $node->getAttribute('method');
                    break;
                }
            }
        
$children = &$node->getChildren(NUMERIC);
        
$j count($children);
        for (
$i 0$i $j; ++$i)
            {
            
$this->_findFormElements$children[$i] );
            }
        }
//}}}end function _findFormElements

    //{{{_handleSelectElem
    /**
     * Responsible for building a representation of a <select> element
     *
     * @param object XmlNode &$node XmlNode object containing the select element
     *                              and it's children
     * @access private
     */
    
function _handleSelectElem( &$node )
        {
        
$options = &$node->getChildren(NUMERIC);
        
$name $node->getAttribute('name');
        
$this->_elements[$name]['selectElem'] = &$node;

        
$j count($options);
        for (
$i 0$i $j; ++$i)
            {
            
$value $options[$i]->getAttribute('value');
            
$value = (empty($value) && $value !== '0')?$options[$i]->getValue():$value;
            
$this->_elements[$name][$value] = &$options[$i];
            }
        }
//}}}end function _handleSelectElem

    //{{{_handleInputElem
    /**
     * Responsible for building a representation of a <input> element
     *
     * @param object XmlNode &$node The node which contains the input elements information.
     * @access private
     */
    
function _handleInputElem( &$node )
        {
        
$type $node->getAttribute('type');
        
$name $node->getAttribute('name');
        switch (
strtolower($type))
            {
            case 
'checkbox':
            case 
'radio':
                
$value $node->getAttribute('value');
                
$this->_elements[$name][$value] = &$node;
                break;
            default:
                
$this->_elements[$name] = &$node;
            }
        }
//}}}end function _handleInputElem

    //{{{addOptGroup
    /**
     * Add an outgroup to the select element identified by $name
     *
     * @param string $name The name appearing in the name attribute of the select element
     * @param string $value The text which is displayed in the dropdown list
     * @access public
     */
    
function addOptGroup$name$value )
        {
        if (!
assert ('isset($this->_elements[$name]) &&
                      isset($this->_elements[$name]["selectElem"])'
))
            {
            
$errorMsg 'Assertion failed in call to Form::addOptGroup:<br/>';
            
$errorMsg .= 'There is no select element with the name '.$name.'<br/>';
            
$errorMsg .= 'Available fieldnames are:<br/><pre>';
            echo 
$errorMsg;
            if (
is_array($this->_elements))
                {
                
print_r(array_keys($this->_elements));
                }
            else
                {
                echo 
"\nNULL\n";
                }
            echo 
'</pre>';
            
$this->_printStackTrace();
            return;
            }

        
$optGroup = new XmlNode('optgroup', array('label' => $value));
        
$this->_elements[$name]['selectElem']->addChild('.'$optGroup);

        
$this->_elements[$name][$value] = &$optGroup;
        }
//}}}end function addOptGroup

    //{{{addOption
    /**
     * Add an option to the select element identified by $name
     *
     * @param string $name The name appearing in the name attribute of the select element
     * @param string $displayValue The text which is displayed in the dropdown list
     * @param string $value If supplied, is the value of the value attribute of
     *                      the <option> element
     * @access public
     */
    
function addOption$name$displayValue$value=''$optGroup '')
        {
        if (!
assert ('isset($this->_elements[$name]) &&
                      isset($this->_elements[$name]["selectElem"]) &&
                      (empty($optGroup) ||
                       isset($this->_elements[$name][$optGroup]))'
))
            {
            
$errorMsg 'Assertion failed in call to Form::addOption:<br/>';
            if (!empty(
$optGroup) && !isset($this->_elements[$name][$optGroup]))
                {
                
$errorMsg 'There is no optGroup, '.$optGroup.' in this(';
                
$errorMsg .= $name.') select element.<br/>';
                }
            else
                {
                
$errorMsg .= 'There is no select element with the name '.$name.'<br/>';
                }
            
$errorMsg .= 'Available fieldnames are:<br/><pre>';
            echo 
$errorMsg;

            if (
is_array($this->_elements))
                {
                
print_r(array_keys($this->_elements));
                }
            else
                {
                echo 
"\nNULL\n";
                }
            echo 
'</pre>';
            
$this->_printStackTrace();
            return;
            }

        
$option = new XmlNode('option'null$displayValue);
        if (
$value !== '')
            
$option->addAttribute('value'$value);

        if (
$optGroup !== '')
            
$this->_elements[$name][$optGroup]->addChild('.'$option);
        else
            
$this->_elements[$name]['selectElem']->addChild('.'$option);

        
$value = ($value === '')?$displayValue:$value;
        
$this->_elements[$name][$value] = &$option;
        }
//}}}end function addOption

    //{{{setRequiredFields
    /**
     * Specify which fields in the form is mandatory
     *
     * @param array $reqFields Array with names of the fields which are
     *                         mandatory in the form
     * @access public
     */
    
function setRequiredFields$reqFields )
        {
        
$this->_reqFields $reqFields;
        }
//}}}end function setRequiredFields

    //{{{formOK
    /**
     * Check all required fields are filled in.
     *
     * @return mixed TRUE if all required fields are filled in. Otherwise
     *                    an array of fieldnames where values are missing.
     * @access public
     */
    
function formOK( )
        {
        
$missingFields = array();
        
$submission = (strtolower($this->_requestMethod) == 'post')?$_POST:$_GET;
        foreach(
$this->_reqFields as $fieldName)
            {
            if (!isset(
$submission[$fieldName]) || empty($submission[$fieldName]))
                {
                
$missingFields[] = $fieldName;
                }
            }
        return (
count($missingFields) === 0)?true:$missingFields;
        }
//}}}end function formOK

    //{{{setValue
    /**
     * Set a fields value. This method can not yet modify the value of <option> elements
     *
     * @param string $fieldName Name of the field which you want to change the value of
     * @param string $value Value to assign to this field
     * @access public
     */
    
function setValue$fieldName$value)
        {
        if (!
assert ('isset($this->_elements[$fieldName]) && !is_array($this->_elements[$fieldName])'))
            {
            
$errorMsg 'Assertion failed in call to Form::setValue:<br/>';
            
$errorMsg .= 'There is no form element with the name '.$fieldName;
            
$errorMsg .= ' or, you are trying to set the value of a select element<br/>';
            
$errorMsg .= 'Available fieldnames are:<br/><pre>';
            echo 
$errorMsg;
            
print_r(array_keys($this->_elements));
            echo 
'</pre>';
            
$this->_printStackTrace();
            return;
            }

        if (
$this->_elements[$fieldName]->getName() == 'textarea')
            
$this->_elements[$fieldName]->setValue($value);
        else
            
$this->_elements[$fieldName]->addAttribute('value'$value);
        }
//}}}end function setValue

    //{{{setSelected
    /**
     * Set the selected attribute of the element identified by $fieldName
     *
     * @param string $fieldName Name of the field which you want to set selected
     * @param string $selectedValue Value of field which you want to set as selected
     * @access public
     */
    
function setSelected$fieldName$selectedValue )
        {
        if (!
assert ('isset($this->_elements[$fieldName]) &&
                      isset($this->_elements[$fieldName][$selectedValue])'
))
            {
            
$errorMsg 'Assertion failed in call to Form::setSelected:<br/>';
            
$errorMsg .= 'There is no form element with the name '.$fieldName;
            
$errorMsg .= ' and the value '.$selectedValue.'<br/>';
            
$errorMsg .= 'Available values for this select element are:<br/><pre>';
            echo 
$errorMsg;
            if (
is_array($this->_elements[$fieldName]))
                
print_r(array_keys($this->_elements[$fieldName]));
            echo 
'</pre>';
            
$this->_printStackTrace();
            return;
            }

        
$type $this->_elements[$fieldName][$selectedValue]->getName();
        if (
'option' == $type)
            
$this->_elements[$fieldName][$selectedValue]->addAttribute('selected''selected');
        else
            
$this->_elements[$fieldName][$selectedValue]->addAttribute('checked''checked');
        }
//}}}end function setSelected

    //{{{getFieldNames
    /**
     * Get an array containing the names off all fields in the form
     *
     * @return array Numeric array of fieldnames
     * @access public
     */
    
function getFieldNames( )
        {
        return 
array_keys($this->_elements);
        }
//}}}end function getFieldNames

    //{{{getForm
    /**
     * Return a string representation of the Form object
     *
     * @return string representation of the Form object
     * @access public
     */
    
function getForm( )
        {
        return 
$this->_xmlTree->getXmlString(NO_WHITESPACE);
        }
//}}}end function getForm

    //{{{getRootNode
    /**
     * Return the root node of the form XML tree
     *
     * @return object XmlNode the root node of the form XML tree
     * @access public
     */
    
function getRootNode( )
        {
        return 
$this->_xmlTree;
        }
//}}}end function getRootNode

    //{{{_printStackTrace
    /**
     * Prints a stacktrace of the callstack
     *
     * @access private
     */
    
function _printStackTrace( )
        {
        
$dbt debug_backtrace();
        echo 
'Complete trace of callstack:<br/>';
        
array_shift($dbt);
        foreach(
$dbt as $eachCall)
            {
            echo 
$eachCall['function'].' called from '.$eachCall['file'].':';
            echo 
$eachCall['line'].'<br/>';
            }
        echo 
'<p/>';
        }
//}}}end function _printStackTrace
    
}
?>