<?php
/*
    File: xajaxControl.inc.php

    Contains the base class for all controls.

    Title: xajaxControl class

    Please see <copyright.inc.php> for a detailed description, copyright
    and license information.
*/

/*
    @package xajax
    @version $Id: xajaxControl.inc.php 362 2007-05-29 15:32:24Z calltoconstruct $
    @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
    @copyright Copyright (c) 2008-2010 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
    @license http://www.xajaxproject.org/bsd_license.txt BSD License
*/

require_once('xajaxRequest.inc.php');

/*
    Constant: XAJAX_HTML_CONTROL_DOCTYPE_FORMAT

    Defines the doctype of the current document; this will effect how the HTML is formatted
    when the html control library is used to construct html documents and fragments.  This can
    be one of the following values:

    'XHTML' - (default)  Typical effects are that certain elements are closed with '/>'
    'HTML' - Typical differences are that closing tags for certain elements cannot be '/>'
*/
if (false == defined('XAJAX_HTML_CONTROL_DOCTYPE_FORMAT')) {
    define('XAJAX_HTML_CONTROL_DOCTYPE_FORMAT', 'XHTML');
}

/*
    Constant: XAJAX_HTML_CONTROL_DOCTYPE_VERSION
*/
if (false == defined('XAJAX_HTML_CONTROL_DOCTYPE_VERSION')) {
    define('XAJAX_HTML_CONTROL_DOCTYPE_VERSION', '1.0');
}

/*
    Constant: XAJAX_HTML_CONTROL_DOCTYPE_VALIDATION
*/
if (false == defined('XAJAX_HTML_CONTROL_DOCTYPE_VALIDATION')) {
    define('XAJAX_HTML_CONTROL_DOCTYPE_VALIDATION', 'TRANSITIONAL');
}


define('XAJAX_DOMRESPONSE_APPENDCHILD', 100);
define('XAJAX_DOMRESPONSE_INSERTBEFORE', 101);
define('XAJAX_DOMRESPONSE_INSERTAFTER', 102);
/*
    Class: xajaxControl

    The base class for all xajax enabled controls.  Derived classes will generate the
    HTML and javascript code that will be sent to the browser via <xajaxControl->printHTML>
    or sent to the browser in a <xajaxResponse> via <xajaxControl->getHTML>.
*/
class xajaxControl
{
    /*
        String: sTag
    */
    protected $sTag;
    
    /*
        Boolean: sEndTag

        'required' - (default) Indicates the control must have a full end tag
        'optional' - The control may have an abbr. begin tag or a full end tag
        'forbidden' - The control must have an abbr. begin tag and no end tag
    */
    protected $sEndTag;
    
    /*
        Array: aAttributes

        An associative array of attributes that will be used in the generation
        of the HMTL code for this control.
    */
    protected $aAttributes;
    
    /*
        Array: aEvents

        An associative array of events that will be assigned to this control.  Each
        event declaration will include a reference to a <xajaxRequest> object; it's
        script will be extracted using <xajaxRequest->printScript> or
        <xajaxRequest->getScript>.
    */
    protected $aEvents;
    
    /*
        String: sClass

        Contains a declaration of the class of this control.  %inline controls do not
        need to be indented, %block controls should be indented.
    */
    protected $sClass;

    /*
        Function: xajaxControl

        Parameters:

        $aConfiguration - (array):  An associative array that contains a variety
            of configuration options for this <xajaxControl> object.

        Note:
        This array may contain the following entries:

        'attributes' - (array):  An associative array containing attributes
            that will be passed to the <xajaxControl->setAttribute> function.

        'children' - (array):  An array of <xajaxControl> derived objects that
            will be the children of this control.
    */
    protected function __construct($sTag, $aConfiguration = array())
    {
        $this->sTag = $sTag;

        $this->clearAttributes();
                
        if (isset($aConfiguration['attributes'])) {
            if (is_array($aConfiguration['attributes'])) {
                foreach ($aConfiguration['attributes'] as $sKey => $sValue) {
                    $this->setAttribute($sKey, $sValue);
                }
            }
        }

        $this->clearEvents();
        
        if (isset($aConfiguration['event'])) {
            call_user_func_array(
                array($this, 'setEvent'),
                $aConfiguration['event']
            );
        } else if (isset($aConfiguration['events'])) {
            if (is_array($aConfiguration['events'])) {
                foreach ($aConfiguration['events'] as $aEvent) {
                    call_user_func_array(
                        array($this, 'setEvent'),
                        $aEvent
                    );
                }
            }
        }
        
        $this->sClass = '%block';
        $this->sEndTag = 'forbidden';
    }
    
    /*
        Function: getClass

        Returns the *adjusted* class of the element
    */
    public function getClass()
    {
        return $this->sClass;
    }

    /*
        Function: clearAttributes

        Removes all attributes assigned to this control.
    */
    public function clearAttributes()
    {
        $this->aAttributes = array();
    }

    /*
        Function: setAttribute

        Call to set various control specific attributes to be included in the HTML
        script that is returned when <xajaxControl->printHTML> or <xajaxControl->getHTML>
        is called.

        Parameters:
            $sName - (string): The attribute name to set the value.
            $sValue - (string): The value to be set.
    */
    public function setAttribute($sName, $sValue)
    {
//SkipDebug
        if (class_exists('clsValidator')) {
            $objValidator = clsValidator::getInstance();
            if (false == $objValidator->attributeValid($this->sTag, $sName)) {
                $objLanguageManager = xajaxLanguageManager::getInstance();
                trigger_error(
                    $objLanguageManager->getText('XJXCTL:IAERR:01')
                    . $sName
                    . $objLanguageManager->getText('XJXCTL:IAERR:02')
                    . $this->sTag
                    . $objLanguageManager->getText('XJXCTL:IAERR:03'),
                    E_USER_ERROR
                );
            }
        }
//EndSkipDebug

        $this->aAttributes[$sName] = $sValue;
    }
    
    /*
        Function: getAttribute

        Call to obtain the value currently associated with the specified attribute
        if set.

        Parameters:

        sName - (string): The name of the attribute to be returned.

        Returns:

        mixed : The value associated with the attribute, or null.
    */
    public function getAttribute($sName)
    {
        if (false == isset($this->aAttributes[$sName])) {
            return null;
        }
        
        return $this->aAttributes[$sName];
    }
    
    /*
        Function: clearEvents

        Clear the events that have been associated with this object.
    */
    public function clearEvents()
    {
        $this->aEvents = array();
    }

    /*
        Function: setEvent

        Call this function to assign a <xajaxRequest> object as the handler for
        the specific DOM event.  The <xajaxRequest->printScript> function will
        be called to generate the javascript for this request.

        Parameters:

        sEvent - (string):  A string containing the name of the event to be assigned.
        objRequest - (xajaxRequest object):  The <xajaxRequest> object to be associated
            with the specified event.
        aParameters - (array, optional):  An array containing parameter declarations
            that will be passed to this <xajaxRequest> object just before the javascript
            is generated.
        sBeforeRequest - (string, optional):  a string containing a snippet of javascript code
            to execute prior to calling the xajaxRequest function
        sAfterRequest - (string, optional):  a string containing a snippet of javascript code
            to execute after calling the xajaxRequest function
    */
    public function setEvent($sEvent, $objRequest, $aParameters = array(), $sBeforeRequest = '', $sAfterRequest = '; return false;')
    {
//SkipDebug
        if (false == ($objRequest instanceof xajaxRequest)) {
            $objLanguageManager = xajaxLanguageManager::getInstance();
            trigger_error(
                $objLanguageManager->getText('XJXCTL:IRERR:01')
                . $this->backtrace(),
                E_USER_ERROR
            );
        }

        if (class_exists('clsValidator')) {
            $objValidator = clsValidator::getInstance();
            if (false == $objValidator->attributeValid($this->sTag, $sEvent)) {
                $objLanguageManager = xajaxLanguageManager::getInstance();
                trigger_error(
                    $objLanguageManager->getText('XJXCTL:IEERR:01')
                    . $sEvent
                    . $objLanguageManager->getText('XJXCTL:IEERR:02')
                    . $this->sTag
                    . $objLanguageManager->getText('XJXCTL:IEERR:03'),
                    E_USER_ERROR
                );
            }
        }
//EndSkipDebug

        $objRequest = clone($objRequest);

        $this->aEvents[$sEvent] = array(
            $objRequest,
            $aParameters,
            $sBeforeRequest,
            $sAfterRequest
            );
    }

    /*
        Function: getHTML

        Generates and returns the HTML representation of this control and
        it's children.

        Returns:

        string : The HTML representation of this control.
    */
    public function getHTML($bFormat = false)
    {
        ob_start();
        if ($bFormat) {
            $this->printHTML();
        } else {
            $this->printHTML(false);
        }
        return ob_get_clean();
    }
    
    /*
        Function: printHTML

        Generates and prints the HTML representation of this control and
        it's children.

        Returns:

        string : The HTML representation of this control.
    */
    public function printHTML($sIndent = '')
    {
//SkipDebug
        if (class_exists('clsValidator')) {
            $objValidator = clsValidator::getInstance();
            $sMissing = '';
            if (false == $objValidator->checkRequiredAttributes($this->sTag, $this->aAttributes, $sMissing)) {
                $objLanguageManager = xajaxLanguageManager::getInstance();
                trigger_error(
                    $objLanguageManager->getText('XJXCTL:MAERR:01')
                    . $sMissing
                    . $objLanguageManager->getText('XJXCTL:MAERR:02')
                    . $this->sTag
                    . $objLanguageManager->getText('XJXCTL:MAERR:03'),
                    E_USER_ERROR
                );
            }
        }
//EndSkipDebug

        $sClass = $this->getClass();

        if ('%inline' != $sClass) {
            // this odd syntax is necessary to detect request for no formatting
            if (false === (false === $sIndent)) {
                echo $sIndent;
            }
        }

        echo '<';
        echo $this->sTag;
        echo ' ';
        $this->_printAttributes();
        $this->_printEvents();

        if ('forbidden' == $this->sEndTag) {
            if ('HTML' == XAJAX_HTML_CONTROL_DOCTYPE_FORMAT) {
                echo '>';
            } else if ('XHTML' == XAJAX_HTML_CONTROL_DOCTYPE_FORMAT) {
                echo '/>';
            }

            if ('%inline' != $sClass) {
                // this odd syntax is necessary to detect request for no formatting
                if (false === (false === $sIndent)) {
                    echo "\n";
                }
            }
        } else if ('optional' == $this->sEndTag) {
            echo '/>';

            if ('%inline' == $sClass) {
                // this odd syntax is necessary to detect request for no formatting
                if (false === (false === $sIndent)) {
                    echo "\n";
                }
            }
        }
//SkipDebug
        else {
            $objLanguageManager = xajaxLanguageManager::getInstance();
            trigger_error(
                $objLanguageManager->getText('XJXCTL:IETERR:01')
                . $this->backtrace(),
                E_USER_ERROR
            );
        }
//EndSkipDebug
    }

    public function getResponse($count, $parent, $flag = XAJAX_DOMRESPONSE_APPENDCHILD)
    {
        $variable = "xjxElm[{$count}]";

        $response = $this->beginGetResponse($variable, $count);
        $this->getResponseAttributes($response, $variable);
        $this->getResponseEvents($response, $variable);
        $this->endGetResponse($response, $variable, $count, $parent, $flag);

        return $response;
    }

    protected function beginGetResponse($variable, $count)
    {
        $response = new xajaxResponse();

        if ($count == 0) {
            $response->domStartResponse();
        }

        $response->domCreateElement($variable, $this->sTag);

        return $response;
    }

    protected function getResponseAttributes($response, $variable)
    {
        foreach ($this->aAttributes as $sKey => $sValue) {
            if ('disabled' != $sKey || 'false' != $sValue) {
                $response->domSetAttribute($variable, $sKey, $sValue);
            }
        }
    }

    protected function endGetResponse($response, $variable, $count, $parent, $flag)
    {
        if ($flag == XAJAX_DOMRESPONSE_APPENDCHILD) {
            $response->domAppendChild($parent, $variable);
        } else if ($flag == XAJAX_DOMRESPONSE_INSERTBEFORE) {
            $response->domInsertBefore($parent, $variable);
        } else if ($flag == XAJAX_DOMRESPONSE_INSERTAFTER) {
            $response->domInsertAfter($parent, $variable);
        }

        if ($count == 0) {
            $response->domEndResponse();
        }
    }

    protected function getResponseEvents($response, $variable)
    {
        foreach (array_keys($this->aEvents) as $sKey) {
            $aEvent = $this->aEvents[$sKey];
            $objRequest = $aEvent[0];
            $aParameters = $aEvent[1];
            $sBeforeRequest = $aEvent[2];
            $sAfterRequest = $aEvent[3];

            foreach ($aParameters as $aParameter) {
                $nParameter = $aParameter[0];
                $sType = $aParameter[1];
                $sValue = $aParameter[2];
                $objRequest->setParameter($nParameter, $sType, $sValue);
            }

            $objRequest->useDoubleQuote();

            $response->script(
                "{$variable}.{$sKey} = function(evt) { " .
                "if (!evt) var evt = window.event; " .
                $sBeforeRequest .
                $objRequest->getScript() .
                $sAfterRequest .
                " } "
            );
        }
    }

    protected function _printAttributes()
    {
        // NOTE: Special case here: disabled='false' does not work in HTML; does work in javascript
        foreach ($this->aAttributes as $sKey => $sValue) {
            if ('disabled' != $sKey || 'false' != $sValue) {
                echo "{$sKey}='{$sValue}' ";
            }
        }
    }

    protected function _printEvents()
    {
        foreach (array_keys($this->aEvents) as $sKey) {
            $aEvent = $this->aEvents[$sKey];
            $objRequest = $aEvent[0];
            $aParameters = $aEvent[1];
            $sBeforeRequest = $aEvent[2];
            $sAfterRequest = $aEvent[3];

            foreach ($aParameters as $aParameter) {
                $nParameter = $aParameter[0];
                $sType = $aParameter[1];
                $sValue = $aParameter[2];
                $objRequest->setParameter($nParameter, $sType, $sValue);
            }

            $objRequest->useDoubleQuote();

            echo "{$sKey}='{$sBeforeRequest}";

            $objRequest->printScript();

            echo "{$sAfterRequest}' ";
        }
    }

    public function backtrace()
    {
        // debug_backtrace was added to php in version 4.3.0
        // version_compare was added to php in version 4.0.7
        if (0 <= version_compare(PHP_VERSION, '4.3.0')) {
            return '<div><div>Backtrace:</div><pre>'
                . print_r(debug_backtrace(), true)
                . '</pre></div>';
        }
        return '';
    }
}

/*
    Class: xajaxControlContainer

    This class is used as the base class for controls that will contain
    other child controls.
*/
class xajaxControlContainer extends xajaxControl
{
    /*
        Array: aChildren

        An array of child controls.
    */
    protected $aChildren;

    /*
        Boolean: sChildClass

        Will contain '%inline' if all children are class = '%inline', '%block' if all children are '%block' or
        '%flow' if both '%inline' and '%block' elements are detected.
    */
    protected $sChildClass;

    /*
        Function: xajaxControlContainer

        Called to construct and configure this control.

        Parameters:

        aConfiguration - (array):  See <xajaxControl->xajaxControl> for more
            information.
    */
    protected function __construct($sTag, $aConfiguration = array())
    {
        parent::__construct($sTag, $aConfiguration);

        $this->clearChildren();
        
        if (isset($aConfiguration['child'])) {
            $this->addChild($aConfiguration['child']);
        } else if (isset($aConfiguration['children'])) {
            $this->addChildren($aConfiguration['children']);
        }
        
        $this->sEndTag = 'required';
    }
    
    /*
        Function: getClass

        Returns the *adjusted* class of the element
    */
    public function getClass()
    {
        $sClass = xajaxControl::getClass();
        
        if (0 < count($this->aChildren) && '%flow' == $sClass) {
            return $this->getContentClass();
        } else if (0 == count($this->aChildren) || '%inline' == $sClass || '%block' == $sClass) {
            return $sClass;
        }
        
        $objLanguageManager = xajaxLanguageManager::getInstance();
        trigger_error(
            $objLanguageManager->getText('XJXCTL:ICERR:01')
            . $this->backtrace(),
            E_USER_ERROR
        );
    }
    
    /*
        Function: getContentClass

        Returns the *adjusted* class of the content (children) of this element
    */
    public function getContentClass()
    {
        $sClass = '';
        
        foreach (array_keys($this->aChildren) as $sKey) {
            if ('' == $sClass) {
                $sClass = $this->aChildren[$sKey]->getClass();
            } else if ($sClass != $this->aChildren[$sKey]->getClass()) {
                return '%flow';
            }
        }
        
        if ('' == $sClass) {
            return '%inline';
        }
            
        return $sClass;
    }
    
    /*
        Function: clearChildren

        Clears the list of child controls associated with this control.
    */
    public function clearChildren()
    {
        $this->sChildClass = '%inline';
        $this->aChildren = array();
    }

    /*
        Function: addChild

        Adds a control to the array of child controls.  Child controls
        must be derived from <xajaxControl>.
    */
    public function addChild($objControl)
    {
//SkipDebug
        if (false == ($objControl instanceof xajaxControl )) {
            $objLanguageManager = xajaxLanguageManager::getInstance();
            trigger_error(
                $objLanguageManager->getText('XJXCTL:ICLERR:01')
                . $this->backtrace(),
                E_USER_ERROR
            );
        }

        if (class_exists('clsValidator')) {
            $objValidator = clsValidator::getInstance();
            if (false == $objValidator->childValid($this->sTag, $objControl->sTag)) {
                $objLanguageManager = xajaxLanguageManager::getInstance();
                trigger_error(
                    $objLanguageManager->getText('XJXCTL:ICLERR:02')
                    . $objControl->sTag
                    . $objLanguageManager->getText('XJXCTL:ICLERR:03')
                    . $this->sTag
                    . $objLanguageManager->getText('XJXCTL:ICLERR:04')
                    . $this->backtrace(),
                    E_USER_ERROR
                );
            }
        }
//EndSkipDebug

        $this->aChildren[] = $objControl;
    }
    
    public function addChildren($aChildren)
    {
//SkipDebug
        if (false == is_array($aChildren)) {
            $objLanguageManager = xajaxLanguageManager::getInstance();
            trigger_error(
                $objLanguageManager->getText('XJXCTL:ICHERR:01')
                . $this->backtrace(),
                E_USER_ERROR
            );
        }
//EndSkipDebug
                
        foreach (array_keys($aChildren) as $sKey) {
            $this->addChild($aChildren[$sKey]);
        }
    }

    public function printHTML($sIndent = '')
    {
//SkipDebug
        if (class_exists('clsValidator')) {
            $objValidator = clsValidator::getInstance();
            $sMissing = '';
            if (false == $objValidator->checkRequiredAttributes($this->sTag, $this->aAttributes, $sMissing)) {
                $objLanguageManager = xajaxLanguageManager::getInstance();
                trigger_error(
                    $objLanguageManager->getText('XJXCTL:MRAERR:01')
                    . $sMissing
                    . $objLanguageManager->getText('XJXCTL:MRAERR:02')
                    . $this->sTag
                    . $objLanguageManager->getText('XJXCTL:MRAERR:03'),
                    E_USER_ERROR
                );
            }
        }
//EndSkipDebug

        $sClass = $this->getClass();
        
        if ('%inline' != $sClass) {
            // this odd syntax is necessary to detect request for no formatting
            if (false === (false === $sIndent)) {
                echo $sIndent;
            }
        }
            
        echo '<';
        echo $this->sTag;
        echo ' ';
        $this->_printAttributes();
        $this->_printEvents();
        
        if (0 == count($this->aChildren)) {
            if ('optional' == $this->sEndTag) {
                echo '/>';

                if ('%inline' != $sClass) {
                    // this odd syntax is necessary to detect request for no formatting
                    if (false === (false === $sIndent)) {
                        echo "\n";
                    }
                }

                return;
            }
//SkipDebug
            else if ('required' != $this->sEndTag) {
                trigger_error(
                    "Invalid end tag designation; should be optional or required.\n"
                    . $this->backtrace(),
                    E_USER_ERROR
                );
            }
//EndSkipDebug
        }
        
        echo '>';
        
        $sContentClass = $this->getContentClass();
        
        if ('%inline' != $sContentClass) {
            // this odd syntax is necessary to detect request for no formatting
            if (false === (false === $sIndent)) {
                echo "\n";
            }
        }

        $this->_printChildren($sIndent);
        
        if ('%inline' != $sContentClass) {
            // this odd syntax is necessary to detect request for no formatting
            if (false === (false === $sIndent)) {
                echo $sIndent;
            }
        }
        
        echo '<' . '/';
        echo $this->sTag;
        echo '>';
        
        if ('%inline' != $sClass) {
            // this odd syntax is necessary to detect request for no formatting
            if (false === (false === $sIndent)) {
                echo "\n";
            }
        }
    }

    protected function _printChildren($sIndent = '')
    {
        if (false == ($this instanceof clsDocument )) {
            // this odd syntax is necessary to detect request for no formatting
            if (false === (false === $sIndent)) {
                $sIndent .= "\t";
            }
        }

        // children
        foreach (array_keys($this->aChildren) as $sKey) {
            $objChild = $this->aChildren[$sKey];
            $objChild->printHTML($sIndent);
        }
    }

    public function getResponse($count, $parent, $flag = XAJAX_DOMRESPONSE_APPENDCHILD)
    {
        $variable = "xjxElm[{$count}]";

        $response = $this->beginGetResponse($variable, $count);
        $this->getResponseAttributes($response, $variable);
        $this->getResponseEvents($response, $variable);
        $this->getResponseChildren($response, $variable, $count);
        $this->endGetResponse($response, $variable, $count, $parent, $flag);

        return $response;
    }

    protected function getResponseChildren($response, $variable, $count)
    {
        foreach (array_keys($this->aChildren) as $sKey) {
            $objChild = $this->aChildren[$sKey];
            $response->appendResponse(
                $objChild->getResponse($count+1, $variable)
            );
        }
    }
}
