qcubed/framework

View on GitHub
includes/base_controls/QFormBase.class.php

Summary

Maintainability
F
2 wks
Test Coverage
<?php
    /**
     * This file contains the QFormBase class.
     *
     * @package Controls
     * @filesource
     */

    /**
     * @package Controls
     * @property-read string     $FormId              Form ID of the QForm
     * @property-read string     $CallType            Type of call (useful when the QForm submits due to user action)
     * @property-read QWaitIcon  $DefaultWaitIcon     Default Ajax wait icon control
     * @property-read integer    $FormStatus          Status of form (pre-render stage, rendering stage of already rendered stage)
     * @property string          $HtmlIncludeFilePath (Alternate) path to the template file to be used
     * @property string          $CssClass            Form CSS class.
     */
    abstract class QFormBase extends QBaseClass {
        ///////////////////////////
        // Static Members
        ///////////////////////////
        /** @var bool True when css scripts get rendered on page. Lets user call RenderStyles in header. */
        protected static $blnStylesRendered = false;

        ///////////////////////////
        // Protected Member Variables
        ///////////////////////////
        /** @var string Form ID (usually passed as the first argument to the 'Run' method call) */
        protected $strFormId;
        /** @var integer representational integer value of what state the form currently is in */
        protected $intFormStatus;
        /** @var QControl[] Array of QControls with this form as the parent */
        protected $objControlArray;
        /**
         * @var QControlGrouping List of Groupings in the form (for old drag and drop)
         * Use of this is deprecated in favor of jQueryUI drag and drop, but code remains in case we need it again.
         * @deprecated
         */
        protected $objGroupingArray;
        /** @var bool Has the body tag already been rendered? */
        protected $blnRenderedBodyTag = false;
        protected $checkableControlValues = array();
        /** @var string The type of call made to the QForm (Ajax, Server or Fresh GET request) */
        protected $strCallType;
        /** @var null|QWaitIcon Default wait icon for the page/QForm  */
        protected $objDefaultWaitIcon = null;

        /** @var array List of included JavaScript files for this QForm */
        protected $strIncludedJavaScriptFileArray = array();
        /** @var array List of ignored JavaScript files for this QForm */
        protected $strIgnoreJavaScriptFileArray = array();

        /** @var array List of included CSS files for this QForm */
        protected $strIncludedStyleSheetFileArray = array();
        /** @var array List of ignored CSS files for this QForm */
        protected $strIgnoreStyleSheetFileArray = array();

        protected $strPreviousRequestMode = false;
        /**
         * @var string The QForm's template file path.
         * When this value is not supplied, the 'Run' function will try to find and use the
         * .tpl.php file with the same filename as the QForm in the same same directory as the QForm file.
         */
        protected $strHtmlIncludeFilePath;
        /** @var string CSS class to be set for the 'form' tag when QCubed Renders the QForm */
        protected $strCssClass;

        protected $strCustomAttributeArray = null;

        ///////////////////////////
        // Form Status Constants
        ///////////////////////////
        /** Form has not started rendering */
        const FormStatusUnrendered = 1;
        /** Form has started rendering but has not finished */
        const FormStatusRenderBegun = 2;
        /** Form rendering has already been started and finished */
        const FormStatusRenderEnded = 3;

        ///////////////////////////
        // Form Preferences
        ///////////////////////////
        /**
         * @var null|string The key to encrypt the formstate
         *              when saving and retrieving from the chosen FormState handler
         */
        public static $EncryptionKey = null;
        /**
         * @var string Chosen FormStateHandler
         *              default is QFormStateHandler as shown here,
         *              however it is read from the configuration.inc.php (in the QForm class)
         *              In case something goes wrong with QForm, the default FormStateHandler here will
         *              try to take care of the situation.
         */
        public static $FormStateHandler = 'QFormStateHandler';

        
        public function __toString() {
            return (string) $this->strFormId;
        }
        /////////////////////////
        // Public Properties: GET
        /////////////////////////
        /**
         * PHP magic method for getting property values of object
         * @param string $strName Name of the propery
         *
         * @return int|mixed|null|string
         * @throws QCallerException
         */
        public function __get($strName) {
            switch ($strName) {
                case "FormId": return $this->strFormId;
                case "CallType": return $this->strCallType;
                case "DefaultWaitIcon": return $this->objDefaultWaitIcon;
                case "FormStatus": return $this->intFormStatus;
                case "HtmlIncludeFilePath": return $this->strHtmlIncludeFilePath;
                case "CssClass": return $this->strCssClass;

                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        /////////////////////////
        // Public Properties: SET
        /////////////////////////
        /**
         * PHP magic function to set the value of properties of class object
         * @param string $strName Name of the property
         * @param string $mixValue Value of the property
         *
         * @return mixed|string
         * @throws QCallerException
         */
        public function __set($strName, $mixValue) {
            switch ($strName) {
                case "HtmlIncludeFilePath":
                    // Passed-in value is null -- use the "default" path name of file".tpl.php"
                    if (!$mixValue) {
                        $strPath = realpath(substr(QApplication::$ScriptFilename, 0, strrpos(QApplication::$ScriptFilename, '.php')) . '.tpl.php');
                        if ($strPath === false) {
                            // Look again based on the object name
                            $strPath = realpath(get_class($this) . '.tpl.php');
                        }
                    }

                    // Use passed-in value
                    else
                        $strPath = realpath($mixValue);

                    // Verify File Exists, and if not, throw exception
                    if (is_file($strPath)) {
                        $this->strHtmlIncludeFilePath = $strPath;
                        return $strPath;
                    } else
                        throw new QCallerException('Accompanying HTML Include File does not exist: "' . $mixValue . '"');
                    break;

                case "CssClass":
                    try {
                        return ($this->strCssClass = QType::Cast($mixValue, QType::String));
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                default:
                    try {
                        return parent::__set($strName, $mixValue);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }


        /////////////////////////
        // Helpers for ControlId Generation
        /////////////////////////

        /**
         * Generates Control ID used to keep track of those QControls whose ID was not explicitly set.
         * It uses the counter variable to maintain uniqueness for Control IDs during the life of the page
         * Life of the page is untill the time when the formstate expired and is removed by the
         * garbage collection of the formstate handler
         * @return string the Ajax Action ID
         */
        public function GenerateControlId() {
//            $strToReturn = sprintf('control%s', $this->intNextControlId);
            $strToReturn = sprintf('c%s', $this->intNextControlId);
            $this->intNextControlId++;
            return $strToReturn;
        }
        /**
         * @var int Counter variable to contain the numerical part of the Control ID value.
         *      it is automatically incremented everytime the GenerateControlId() runs
         */
        protected $intNextControlId = 1;

        /////////////////////////
        // Helpers for AjaxActionId Generation
        /////////////////////////
        /**
         * Generates Ajax Action ID used to keep track of Ajax Actions
         * It uses the counter variable to maintain uniqueness for Ajax Action IDs during the life of the page
         * Life of the page is untill the time when the formstate expired and is removed by the
         * garbage collection of the formstate handler
         * @return string the Ajax Action ID
         */
        public function GenerateAjaxActionId() {
            $strToReturn = sprintf('a%s', $this->intNextAjaxActionId);
            $this->intNextAjaxActionId++;
            return $strToReturn;
        }

        /**
         * @var int Counter variable to contain the numerical part of the AJAX ID value.
         *      it is automatically incremented everytime the GenerateAjaxActionId() runs
         */
        protected $intNextAjaxActionId = 1;

        /////////////////////////
        // Event Handlers
        /////////////////////////
        /**
         * Custom Form Run code.
         * To contain code which should be run 'AFTER' QCubed's QForm run has been completed
         * but 'BEFORE' the custom event handlers are called
         * (In case it is to be used, it should be overriden by a child class)
         */
        protected function Form_Run() {}

        /**
         * To contain the code which should be executed after the Form Run and
         * before the custom handlers are called (In case it is to be used, it should be overridden by a child class)
         * In this situation, we are about to process an event, or the user has reloaded the page. Do whatever you
         * need to do before any event processing.
         */
        protected function Form_Load() {}

        /**
         * To contain the code to initialize the QForm on the first call.
         * Once the QForm is created, the state is saved and is reused by the Run method.
         * In short - this function will run only once (the first time the QForm is to be created)
         * (In case it is to be used, it should be overriden by a child class)
         */
        protected function Form_Create() {}

        /**
         * To contain the code to be executed after Form_Run, Form_Create, Form_Load has been called
         * and the custom defined event handlers have been executed but actual rendering process has not begun.
         * This is a good place to put data into a session variable that you need to send to
         * other forms.
         */
        protected function Form_PreRender() {}

        /**
         * Override this method to set data in your form controls. Appropriate things to do would be to:
         * - Respond to options sent by _GET or _POST variables.
         * - Load data into the control from the database
         * - Initialize controls whose data depends on the state or data in other controls.
         *
         * When this is called, the controls will have been created by Form_Create, and will have already read their saved state.
         *
         */
        protected function Form_Initialize() {}

        /**
         * The Form_Validate method.
         *
         * Before we get here, all the controls will first be validated. Override this method to do
         * additional form level validation, and any form level actions needed as part of the validation process,
         * like displaying an error message.
         *
         * This is the last thing called in the validation process, and will always be called if
         * validation is requested, even if prior controls caused a validation error. Return false to prevent
         * validation and cancel the current action.
         *
         * $blnValid will contain the result of control validation. If it is false, you know that validation will
         * fail, regardless of what you return from the function.
         *
         * @return bool     Return false to prevent validation.
         */
        protected function Form_Validate() {return true;}

        /**
         * If you want to respond in some way to an invalid form that you have not already been able to handle,
         * override this function. For example, you could display a message that an error occurred with some of the
         * controls.
         */
        protected function Form_Invalid() {}

        /**
         * This function is meant to be overriden by child class and is called when the Form exits
         * (After the form render is complete and just before the Run function completes execution)
         */
        protected function Form_Exit() {}


        /**
         * VarExport the Controls or var_export the current QForm
         * (well, be ready for huge amount of text)
         * @param bool $blnReturn
         *
         * @return mixed
         */
        public function VarExport($blnReturn = true) {
            if ($this->objControlArray) foreach ($this->objControlArray as $objControl)
                $objControl->VarExport(false);
            if ($blnReturn) {
                return var_export($this, true);
            }
            else {
                return null;
            }
        }

        /**
         * Returns the value of a checkable control. Checkable controls are special, in that the browser only tells us
         * when a control is checked, not when it is unchecked. So, unless we keep track of them specially, we will
         * not know if they are unchecked, or just not there.
         * @param $strControlId
         * @return mixed|null
         */
        public function CheckableControlValue($strControlId) {
            if (array_key_exists($strControlId, $this->checkableControlValues)) {
                return $this->checkableControlValues[$strControlId];
            }
            return null;
        }


        /**
         * Helper function for below GetModifiedControls
         * @param QControl $objControl
         * @return boolean
         */
        protected static function IsControlModified ($objControl) {
            return $objControl->IsModified();
        }
        /**
         * Return only the controls that have been modified
         */
        public function GetModifiedControls() {
            $ret = array_filter ($this->objControlArray, 'QForm::IsControlModified');
            return $ret;
        }

        /**
         * This method initializes the actual layout of the form
         * It runs in all cases including initial form (the time when Form_Create is run) as well as on
         * trigger actions (QServerAction, QAjaxAction, QServerControlAction and QAjaxControlAction)
         *
         * It is responsible for implementing the logic and sequence in which page wide checks are done
         * such as running Form_Validate and Control validations for every control of the page and their
         * child controls. Checking for an existing FormState and loading them before trigerring any action
         * is also a responsibility of this method.
         * @param string $strFormClass The class of the form to create when creating a new form.
         * @param string|null $strAlternateHtmlFile location of the alternate HTML template file.
         * @param string|null $strFormId The html id to use for the form. If null, $strFormClass will be used.
         *
         * @throws QCallerException
         * @throws QInvalidFormStateException
         * @throws Exception
         */
        public static function Run($strFormClass, $strAlternateHtmlFile = null, $strFormId = null) {
            // See if we can get a Form Class out of PostData
            $objClass = null;
            if ($strFormId === null) {
                $strFormId = $strFormClass;
            }
            if (array_key_exists('Qform__FormId', $_POST) && ($_POST['Qform__FormId'] == $strFormId) && array_key_exists('Qform__FormState', $_POST)) {
                $strPostDataState = $_POST['Qform__FormState'];

                if ($strPostDataState)
                    // We might have a valid form state -- let's see by unserializing this object
                    $objClass = QForm::Unserialize($strPostDataState);

                // If there is no QForm Class, then we have an Invalid Form State
                if (!$objClass) {
                    self::InvalidFormState();
                }
            }
            
            if ($objClass) {
                // Globalize
                global $_FORM;
                $_FORM = $objClass;

                $objClass->strCallType = $_POST['Qform__FormCallType'];
                $objClass->intFormStatus = QFormBase::FormStatusUnrendered;

                if ($objClass->strCallType == QCallType::Ajax) {
                    QApplication::$RequestMode = QRequestMode::Ajax;
                }

                // Cleanup ajax post data if the encoding does not match, since ajax data is always utf-8
                if ($objClass->strCallType == QCallType::Ajax && QApplication::$EncodingType != 'UTF-8') {
                    foreach ($_POST as $key=>$val) {
                        if (substr($key, 0, 6) != 'Qform_') {
                            $_POST[$key] = iconv('UTF-8', QApplication::$EncodingType, $val);
                        }
                    }
                }

                if (!empty($_POST['Qform__FormParameter'])) {
                    $_POST['Qform__FormParameter'] = self::UnpackPostVar($_POST['Qform__FormParameter']);
                }

                // Decode custom post variables from server calls
                if (!empty($_POST['Qform__AdditionalPostVars'])) {
                    $val = self::UnpackPostVar($_POST['Qform__AdditionalPostVars']);
                    $_POST = array_merge($_POST, $val);
                }

                // Iterate through all the control modifications
                if (!empty($_POST['Qform__FormUpdates'])) {
                    $controlUpdates = $_POST['Qform__FormUpdates'];
                    if (is_string($controlUpdates)) {    // Server post is encoded, ajax not encoded
                        $controlUpdates = self::UnpackPostVar($controlUpdates);
                    }
                    if (!empty($controlUpdates)) {
                        foreach ($controlUpdates as $strControlId=>$params) {
                            foreach ($params as $strProperty=>$strValue) {
                                switch ($strProperty) {
                                    case 'Parent':
                                        if ($strValue) {
                                            if ($strValue == $objClass->FormId) {
                                                $objClass->objControlArray[$strControlId]->SetParentControl(null);
                                            } else {
                                                $objClass->objControlArray[$strControlId]->SetParentControl($objClass->objControlArray[$strValue]);
                                            }
                                        } else {
                                            // Remove all parents
                                            $objClass->objControlArray[$strControlId]->SetParentControl(null);
                                            $objClass->objControlArray[$strControlId]->SetForm(null);
                                            $objClass->objControlArray[$strControlId] = null;
                                            unset($objClass->objControlArray[$strControlId]);
                                        }
                                        break;
                                    default:
                                        if (array_key_exists($strControlId, $objClass->objControlArray))
                                            $objClass->objControlArray[$strControlId]->__set($strProperty, $strValue);
                                        break;

                                }
                            }
                        }
                    }
                }


                // Set the RenderedCheckableControlArray
                if (!empty($_POST['Qform__FormCheckableControls'])) {
                    $vals = $_POST['Qform__FormCheckableControls'];
                    if (is_string($vals)) { // Server post is encoded, ajax not encoded
                        $vals = self::UnpackPostVar($vals);
                    }
                    $objClass->checkableControlValues = $vals;
                } else {
                    $objClass->checkableControlValues = [];
                }

                // This is original code. In an effort to minimize changes,
                // we aren't going to touch the server calls for now
                if ($objClass->strCallType != QCallType::Ajax) {
                    foreach ($objClass->objControlArray as $objControl) {
                        // If they were rendered last time and are visible 
                        // (and if ServerAction, enabled), then Parse its post data
                        if (($objControl->Visible) &&
                            ($objControl->Enabled) &&
                            ($objControl->RenderMethod)) {
                            // Call each control's ParsePostData()
                            $objControl->ParsePostData();
                        }

                        // Reset the modified/rendered flags and the validation
                        // in ALL controls
                        $objControl->ResetFlags();
                    }
                }
                else {
                    // Ajax post. Only send data to controls specified in the post to save time.

                    $previouslyFoundArray = array();
                    $controls = $_POST;
                    $controls = array_merge($controls, $objClass->checkableControlValues);
                    foreach ($controls as $key=>$val) {
                        if ($key == 'Qform__FormControl') {
                            $strControlId = $val;
                        } elseif (substr($key, 0, 6) == 'Qform_') {
                            continue;    // ignore this form data
                        } else {
                            $strControlId = $key;
                        }
                        if (($intOffset = strpos ($strControlId, '_')) !== false) {    // the first break is the control id
                            $strControlId = substr ($strControlId, 0, $intOffset);
                        }
                        if (($objControl = $objClass->GetControl($strControlId)) &&
                                !isset($previouslyFoundArray[$strControlId])) {
                            if (($objControl->Visible) &&
                                ($objControl->RenderMethod)) {
                                // Call each control's ParsePostData()
                                $objControl->ParsePostData();
                            }

                            $previouslyFoundArray[$strControlId] = true;
                        }
                    }
                }

                // Only if our action is validating, we are going to reset the validation state of all the controls
                if (isset($_POST['Qform__FormControl']) && isset($objClass->objControlArray[$_POST['Qform__FormControl']])) {
                    $objControl = $objClass->objControlArray[$_POST['Qform__FormControl']];
                    if ($objControl->CausesValidation) {
                        $objClass->ResetValidationStates();
                    }
                }

                // Trigger Run Event (if applicable)
                $objClass->Form_Run();

                // Trigger Load Event (if applicable)
                $objClass->Form_Load();

                // Trigger a triggered control's Server- or Ajax- action (e.g. PHP method) here (if applicable)
                $objClass->TriggerActions();

            } else {
                // We have no form state -- Create Brand New One
                $objClass = new $strFormClass();

                // Globalize
                global $_FORM;
                $_FORM = $objClass;

                // Setup HTML Include File Path, based on passed-in strAlternateHtmlFile (if any)
                try {
                    $objClass->HtmlIncludeFilePath = $strAlternateHtmlFile;
                } catch (QCallerException $objExc) {
                    $objExc->IncrementOffset();
                    throw $objExc;
                }

                // By default, this form is being created NOT via a PostBack
                // So there is no CallType
                $objClass->strCallType = QCallType::None;

                $objClass->strFormId = $strFormId;
                $objClass->intFormStatus = QFormBase::FormStatusUnrendered;
                $objClass->objControlArray = array();
                $objClass->objGroupingArray = array();

                // Trigger Run Event (if applicable)
                $objClass->Form_Run();

                // Trigger Create Event (if applicable)
                $objClass->Form_Create();

                $objClass->Form_Initialize();

                if (defined ('__DESIGN_MODE__') && __DESIGN_MODE__ == 1) {
                    // Attach custom event to dialog to handle right click menu items sent by form

                    $dlg = new QModelConnectorEditDlg ($objClass, 'qconnectoreditdlg');

                    $dlg->AddAction (
                        new QOnEvent('qdesignerclick'),
                        new QAjaxAction ('ctlDesigner_Click', null, null, 'ui')
                    );
                }

            }

            // Trigger PreRender Event (if applicable)
            $objClass->Form_PreRender();

            // Render the Page
            switch ($objClass->strCallType) {
                case QCallType::Ajax:
                    // Must use AJAX-based renderer
                    $objClass->RenderAjax();
                    break;

                case QCallType::Server:
                case QCallType::None:
                case '':
                    // Server/Postback or New Page
                    // Make sure all controls are marked as not being on the page yet
                    foreach ($objClass->objControlArray as $objControl)
                        $objControl->ResetOnPageStatus();

                    // Use Standard Rendering
                    $objClass->Render();

                    // Ensure that RenderEnd() was called during the Render process
                    switch ($objClass->intFormStatus) {
                        case QFormBase::FormStatusUnrendered:
                            throw new QCallerException('$this->RenderBegin() is never called in the HTML Include file');
                        case QFormBase::FormStatusRenderBegun:
                            throw new QCallerException('$this->RenderEnd() is never called in the HTML Include file');
                        case QFormBase::FormStatusRenderEnded:
                            break;
                        default:
                            throw new QCallerException('FormStatus is in an unknown status');
                    }
                break;

                default:
                    throw new Exception('Unknown Form CallType: ' . $objClass->strCallType);
            }

            // Once all the controls have been set up, and initialized, remember them.
            $objClass->SaveControlState();

            // Tigger Exit Event (if applicable)
            $objClass->Form_Exit();
        }

        /**
         * Unpacks a post variable that has been encoded with JSON.stringify.
         *
         * @param $val
         * @return mixed|string
         */
        protected static function UnpackPostVar($val) {
            if (QApplication::$EncodingType != 'UTF-8' && QApplication::$RequestMode != QRequestMode::Ajax) {
                // json_decode only accepts utf-8 encoded text. Ajax calls are already UTF-8 encoded.
                $val = iconv(QApplication::$EncodingType, 'UTF-8', $val);
            }
            $val = json_decode($val, true);
            if (QApplication::$EncodingType != 'UTF-8') {
                // Must convert back from utf-8 to whatever our application encoding is
                if (is_string($val)) {
                    $val = iconv('UTF-8', QApplication::$EncodingType, $val);
                }
                elseif (is_array($val)) {
                    array_walk_recursive($val, function(&$v, $key) {
                        if (is_string($v)) {
                            $v = iconv('UTF-8', QApplication::$EncodingType, $v);
                        }
                    });
                }
                else {
                    throw new Exception ('Unknown Post Var Type');
                }
            }
            return $val;
        }

        /**
         * Reset all validation states.
         */
        public function ResetValidationStates() {
            foreach ($this->objControlArray as $objControl) {
                $objControl->ValidationReset();
            }
        }

        /**
         * Private function to respond to a designer click.
         *
         * @param $strFormId
         * @param $strControlId
         * @param $mixParam
         */
        private function ctlDesigner_Click ($strFormId, $strControlId, $mixParam) {
            if (isset($mixParam['id'])) {
                $controlId = $mixParam['id'];
                if (strpos($controlId, '_')) {    // extra the real control id from a sub id
                    $controlId = substr($controlId, 0, strpos($controlId, '_'));
                }
            }
            elseif (isset($mixParam['for'])) {
                $controlId = $mixParam['for'];
            }
            if (!empty($controlId)) {
                $objControl = $this->GetControl($controlId);
                if ($objControl) {
                    $dlg = $this->GetControl ('qconnectoreditdlg');
                    $dlg->EditControl ($objControl);
                }
            }
        }

        /**
         * An invalid form state was found. 
         * We were handed a formstate, but the formstate could not be interpreted. This could be for
         * a variety of reasons, and is dependent on the formstate handler. Most likely, the user hit
         * the back button past the back button limit of what we remember, or the user lost the session.
         * Or, you simply have not set up the form state handler correctly.
         * In the past, we threw an exception, but that was not a very user friendly response. 
         * The response below resubmits the url without a formstate so that a new one will be created. 
         * Override if you want a different response.
         */
        public static function InvalidFormState() {
            //ob_clean();
            if (isset($_POST['Qform__FormCallType']) &&  $_POST['Qform__FormCallType'] == QCallType::Ajax) {
                QApplication::$ProcessOutput = false;
                QApplication::SendAjaxResponse(['loc' => 'reload']);
            } else {
                header('Location: '. QApplication::$RequestUri);
            }

            // End the Response Script
            exit();    
        }

        /**
         * Calls a data binder associated with the form. Does this so data binder can be protected. Mostly for legacy code.
         * @param callable $callable
         * @param  QControl $objPaginatedControl
         * @throws QDataBindException
         */
        public function CallDataBinder($callable, $objPaginatedControl) {
            try {
                call_user_func($callable, $objPaginatedControl);
            } catch (QCallerException $objExc) {
                throw new QDataBindException($objExc);
            }
        }

        /**
         * Renders the AjaxHelper for the QForm
         * @param QControlBase $objControl
         *
         * @return string The Ajax helper string (should be JS commands)
         */
        protected function RenderAjaxHelper($objControl) {
            $controls = [];

            if ($objControl) {
                $controls = array_merge($controls, $objControl->RenderAjax());    // will return an array of controls to be merged with current controls
                foreach ($objControl->GetChildControls() as $objChildControl) {
                    $controls = array_merge($controls, $this->RenderAjaxHelper($objChildControl));
                }
            }

            return $controls;
        }

        /**
         * Renders the actual ajax return value as a json object. Since json must be UTF-8 encoded, will convert to
         * UTF-8 if needed. Response is parsed in the "success" function in qcubed.js, and handled there.
         */
        protected function RenderAjax() {
            $aResponse = array();

            if (QApplication::$JavascriptExclusiveCommand) {
                /**
                 * Processing of the actions has resulted in a very high priority exclusive response. This would typically
                 * happen when a javascript widget is requesting data from us. We want to respond as quickly as possible,
                 * and also prevent possibly redrawing the widget while its already in the middle of its own drawing.
                 * We short-circuit the drawing process here.
                 */

                $aResponse = QApplication::GetJavascriptCommandArray();
                $strFormState = QForm::Serialize($this);
                $aResponse[QAjaxResponse::Controls][] = [QAjaxResponse::Id=>"Qform__FormState", QAjaxResponse::Value=>$strFormState];    // bring it back next time
                ob_clean();
                QApplication::SendAjaxResponse($aResponse);
                return;
            }

            // Update the Status
            $this->intFormStatus = QFormBase::FormStatusRenderBegun;

            // Broadcast the watcher change to other windows listening
            if (QWatcher::WatchersChanged()) {
                $aResponse[QAjaxResponse::Watcher] = true;
            }

            // Recursively render changed controls, starting with all top-level controls
            $controls = array();
            foreach ($this->GetAllControls() as $objControl) {
                if (!$objControl->ParentControl) {
                    $controls = array_merge($controls, $this->RenderAjaxHelper($objControl));
                }
            }
            $aResponse[QAjaxResponse::Controls] = $controls;

            // Go through all controls and gather up any JS or CSS to run or Form Attributes to modify
            foreach ($this->GetAllControls() as $objControl) {
                // Include any javascript files that were added by the control
                // Note: current implementation does not handle removal of javascript files
                if ($strScriptArray = $this->ProcessJavaScriptList($objControl->JavaScripts)) {
                    QApplication::AddJavaScriptFiles($strScriptArray);
                }

                // Include any new stylesheets
                if ($strScriptArray = $this->ProcessStyleSheetList($objControl->StyleSheets)) {
                    QApplication::AddStyleSheets(array_keys($strScriptArray));
                }

                // Form Attributes
                $attributes = $objControl->_GetFormAttributes();
                if ($attributes) {
                    // Make sure the form has attributes that the control requires.
                    // Note that current implementation does not handle removing attributes that are no longer needed
                    // if such a control gets removed from a form during an ajax call, but that is a very unlikely scenario.
                    QApplication::ExecuteControlCommand($this->strFormId, 'attr', $attributes);
                }
            }

            $strControlIdToRegister = array();
            foreach ($this->GetAllControls() as $objControl) {
                $strScript = '';
                if ($objControl->Rendered) { // whole control was rendered during this event
                    $strScript = trim ($objControl->GetEndScript());
                    $strControlIdToRegister[] = $objControl->ControlId;
                } else {
                    $objControl->RenderAttributeScripts(); // render one-time attribute commands only
                }
                if ($strScript) {
                    QApplication::ExecuteJavaScript($strScript, QJsPriority::High);    // put these last in the high priority queue, just before getting the commands below
                }
                $objControl->ResetFlags();
            }

            if ($strControlIdToRegister) {
                $aResponse[QAjaxResponse::RegC] = $strControlIdToRegister;
            }


            foreach ($this->objGroupingArray as $objGrouping) {
                $strRender = $objGrouping->Render();
                if (trim($strRender))
                    QApplication::ExecuteJavaScript($strRender, QJsPriority::High);
            }


            $aResponse = array_merge($aResponse, QApplication::GetJavascriptCommandArray());

            // Add in the form state
            $strFormState = QForm::Serialize($this);
            $aResponse[QAjaxResponse::Controls][] = [QAjaxResponse::Id=>"Qform__FormState", QAjaxResponse::Value=>$strFormState];

            $strContents = trim(ob_get_contents());

            if (strtolower(substr($strContents, 0, 5)) == 'debug') {
                // TODO: Output debugging information.
            } else {
                ob_clean();

                QApplication::SendAjaxResponse($aResponse);
            }

            // Update Render State
            $this->intFormStatus = QFormBase::FormStatusRenderEnded;
        }

        /**
         * Saves the formstate using the 'Save' method of FormStateHandler set in configuration.inc.php
         * @param QForm $objForm
         *
         * @return string the Serialized QForm
         */
        public static function Serialize(QForm $objForm) {
            // Get and then Update PreviousRequestMode
            $strPreviousRequestMode = $objForm->strPreviousRequestMode;
            $objForm->strPreviousRequestMode = QApplication::$RequestMode;

            // Figure Out if we need to store state for back-button purposes
            $blnBackButtonFlag = true;
            if ($strPreviousRequestMode == QRequestMode::Ajax)
                $blnBackButtonFlag = false;

            // Create a Clone of the Form to Serialize
            $objForm = clone($objForm);

            // Cleanup internal links between controls and the form
            if ($objForm->objControlArray) foreach ($objForm->objControlArray as $objControl) {
                $objControl->Sleep();
            }

            // Use PHP "serialize" to serialize the form
            $strSerializedForm = serialize($objForm);

            // Setup and Call the FormStateHandler to retrieve the PostDataState to return
            $strFormStateHandler = QForm::$FormStateHandler;
            $strPostDataState = $strFormStateHandler::Save ($strSerializedForm, $blnBackButtonFlag);

            // Return the PostDataState
            return $strPostDataState;
        }

        /**
         * Unserializes (extracts) the FormState using the 'Load' method of FormStateHandler set in configuration.inc.php
         * @param string $strPostDataState The string identifying the FormState to the loaded for Unserialization
         *
         * @internal param string $strSerializedForm
         * @return QForm the Form object
         */
        public static function Unserialize($strPostDataState) {
            // Setup and Call the FormStateHandler to retrieve the Serialized Form
            $strFormStateHandler = QForm::$FormStateHandler;
            $strSerializedForm = $strFormStateHandler::Load ($strPostDataState);

            if ($strSerializedForm) {
                // Unserialize and Cast the Form
                // For the QSessionFormStateHandler the __PHP_Incomplete_Class occurs sometimes
                // for the result of the unserialize call.
                $objForm = unserialize($strSerializedForm);
                $objForm = QType::Cast($objForm, 'QForm');

                // Reset the links from Control->Form
                if ($objForm->objControlArray) foreach ($objForm->objControlArray as $objControl) {
                    // If you are having trouble with a __PHP_Incomplete_Class here, it means you are not including the definitions
                    // of your own controls in the form.
                    $objControl->Wakeup($objForm);
                }

                // Return the Form
                return $objForm;
            } else
                return null;
        }

        /**
         * Add a QControl to the current QForm.
         * @param QControl|QControlBase $objControl
         *
         * @throws QCallerException
         */
        public function AddControl(QControl $objControl) {
            $strControlId = $objControl->ControlId;
            $objControl->MarkAsModified(); // make sure new controls get drawn
            if (array_key_exists($strControlId, $this->objControlArray))
                throw new QCallerException(sprintf('A control already exists in the form with the ID: %s', $strControlId));
            if (array_key_exists($strControlId, $this->objGroupingArray))
                throw new QCallerException(sprintf('A Grouping already exists in the form with the ID: %s', $strControlId));
            $this->objControlArray[$strControlId] = $objControl;
        }

        /**
         * Returns a control from the current QForm
         * @param string $strControlId The Control ID of the control which is needed to be fetched
         *               from the current QForm (should be the child of the current QForm).
         *
         * @return null|QControl
         */
        public function GetControl($strControlId) {
            if (isset($this->objControlArray[$strControlId])) {
                return $this->objControlArray[$strControlId];
            }
            else {
                return null;
            }
        }

        /**
         * Removes a QControl (and its children) from the current QForm
         * @param string $strControlId
         */
        public function RemoveControl($strControlId) {
            if (isset($this->objControlArray[$strControlId])) {
                // Get the Control in Question
                $objControl = $this->objControlArray[$strControlId];

                // Remove all Child Controls as well
                $objControl->RemoveChildControls(true);

                // Remove this control from the parent
                if ($objControl->ParentControl) {
                    $objControl->ParentControl->RemoveChildControl($strControlId, false);    // will redraw the ParentControl
                } else {
                    // if the parent is the form, then remove it from the dom through javascript, since the form won't be redrawn
                    QApplication::ExecuteSelectorFunction('#' . $objControl->GetWrapperId(), 'remove');
                }

                // Remove this control
                unset($this->objControlArray[$strControlId]);

                // Remove this control from any groups
                foreach ($this->objGroupingArray as $strKey => $objGrouping)
                    $this->objGroupingArray[$strKey]->RemoveControl($strControlId);
            }
        }

        /**
         * Returns all controls belonging to the Form as an array.
         * @return mixed|QControl[]
         */
        public function GetAllControls() {
            return $this->objControlArray;
        }

        /**
         * Tell all the controls to save their state.
         */
        public function SaveControlState() {
            // tell the controls to save their state
            $a = $this->GetAllControls();
            foreach ($a as $control) {
                $control->_WriteState();
            }
        }

        /**
         * Tell all the controls to read their state.
         */
        protected function RestoreControlState() {
            // tell the controls to restore their state
            $a = $this->GetAllControls();
            foreach ($a as $control) {
                $control->_ReadState();
            }
        }


        /**
         * Custom Attributes are other html name-value pairs that can be rendered within the form using this method.
         * For example, you can now render the autocomplete tag on the QForm
         * additional javascript actions, etc.
         *         $this->SetCustomAttribute("autocomplete", "off");
         * Will render:
         *          [form ...... autocomplete="off"] (replace sqare brackets with angle brackets)
         * @param string $strName Name of the attribute
         * @param string $strValue Value of the attribute
         *
         * @throws QCallerException
         */
        public function SetCustomAttribute($strName, $strValue) {
            if ($strName == "method" || $strName == "action")
                throw new QCallerException(sprintf("Custom Attribute not supported through SetCustomAttribute(): %s", $strName));

            if (!is_null($strValue))
                $this->strCustomAttributeArray[$strName] = $strValue;
            else {
                $this->strCustomAttributeArray[$strName] = null;
                unset($this->strCustomAttributeArray[$strName]);
            }
        }

        /**
         * Returns the requested custom attribute from the form.
         * This attribute must have already been set.
         * @param string $strName Name of the Custom Attribute
         *
         * @return mixed
         * @throws QCallerException
         */
        public function GetCustomAttribute($strName) {
            if ((is_array($this->strCustomAttributeArray)) && (array_key_exists($strName, $this->strCustomAttributeArray)))
                return $this->strCustomAttributeArray[$strName];
            else
                throw new QCallerException(sprintf("Custom Attribute does not exist in Form: %s", $strName));
        }

        public function RemoveCustomAttribute($strName) {
            if ((is_array($this->strCustomAttributeArray)) && (array_key_exists($strName, $this->strCustomAttributeArray))) {
                $this->strCustomAttributeArray[$strName] = null;
                unset($this->strCustomAttributeArray[$strName]);
            } else
                throw new QCallerException(sprintf("Custom Attribute does not exist in Form: %s", $strName));
        }


        public function AddGrouping(QControlGrouping $objGrouping) {
            $strGroupingId = $objGrouping->GroupingId;
            if (array_key_exists($strGroupingId, $this->objGroupingArray))
                throw new QCallerException(sprintf('A Grouping already exists in the form with the ID: %s', $strGroupingId));
            if (array_key_exists($strGroupingId, $this->objControlArray))
                throw new QCallerException(sprintf('A Control already exists in the form with the ID: %s', $strGroupingId));
            $this->objGroupingArray[$strGroupingId] = $objGrouping;
        }

        public function GetGrouping($strGroupingId) {
            if (array_key_exists($strGroupingId, $this->objGroupingArray))
                return $this->objGroupingArray[$strGroupingId];
            else
                return null;
        }

        public function RemoveGrouping($strGroupingId) {
            if (array_key_exists($strGroupingId, $this->objGroupingArray)) {
                // Remove this Grouping
                unset($this->objGroupingArray[$strGroupingId]);
            }
        }

        /**
         * Retruns the Groupings
         * @return mixed
         */
        public function GetAllGroupings() {
            return $this->objGroupingArray;
        }

        /**
         * Returns the child controls of the current QForm or a QControl object
         *
         * @param QForm|QControl|QFormBase $objParentObject The object whose child controls are to be searched for
         *
         * @throws QCallerException
         * @return QControl[]
         */
        public function GetChildControls($objParentObject) {
            $objControlArrayToReturn = array();

            if ($objParentObject instanceof QForm) {
                // They want all the ChildControls for this Form
                // Basically, return all objControlArray QControls where the Qcontrol's parent is NULL
                foreach ($this->objControlArray as $objChildControl) {
                    if (!($objChildControl->ParentControl))
                        array_push($objControlArrayToReturn, $objChildControl);
                }
                return $objControlArrayToReturn;

            } else if ($objParentObject instanceof QControl) {
                return $objParentObject->GetChildControls();
                // THey want all the ChildControls for a specific Control
                // Basically, return all objControlArray QControls where the Qcontrol's parent is the passed in parentobject
                /*                $strControlId = $objParentObject->ControlId;
                                foreach ($this->objControlArray as $objChildControl) {
                                    $objParentControl = $objChildControl->ParentControl;
                                    if (($objParentControl) && ($objParentControl->ControlId == $strControlId)) {
                                        array_push($objControlArrayToReturn, $objChildControl);
                                    }
                                }*/

            } else
                throw new QCallerException('ParentObject must be either a QForm or QControl object');
        }

        /**
         * This function evaluates the QForm Template.
         * It will try to open the Template file specified in the call to 'Run' method for the QForm
         * and then execute it.
         * @param string $strTemplate Path to the HTML template file
         *
         * @return string The evaluated HTML string
         */
        public function EvaluateTemplate($strTemplate) {
            global $_ITEM;
            global $_CONTROL;
            global $_FORM;

            if ($strTemplate) {
                QApplication::$ProcessOutput = false;
                // Store the Output Buffer locally
                $strAlreadyRendered = ob_get_contents();
                if ($strAlreadyRendered) {
                    ob_clean();
                }

                // Evaluate the new template
                ob_start('__QForm_EvaluateTemplate_ObHandler');
                require($strTemplate);
                $strTemplateEvaluated = ob_get_contents();
                ob_end_clean();

                // Restore the output buffer and return evaluated template
                if ($strAlreadyRendered) {
                    print($strAlreadyRendered);
                }
                QApplication::$ProcessOutput = true;

                return $strTemplateEvaluated;
            } else
                return null;
        }

        /**
         * Triggers an event handler method for a given control ID
         * NOTE: Parameters must be already validated and are guaranteed to exist.
         *
         * @param string $strControlId  Control ID triggering the method
         * @param string $strMethodName Method name which has to be fired. Includes a control id if a control action.
         * @param QAction $objAction The action object which caused the event
         */
        protected function TriggerMethod($strControlId, $strMethodName, QAction $objAction) {
            $mixParameter = $_POST['Qform__FormParameter'];
            $objSourceControl = $this->objControlArray[$strControlId];
            $params = QControl::_ProcessActionParams($objSourceControl, $objAction, $mixParameter);

            if (strpos($strMethodName, '::')) {
                // Calling a static method in a class
                $f = explode('::', $strMethodName);
                if (is_callable($f)) {
                    $f($this, $params['controlId'], $params['param'], $params);
                }
            }
            elseif (($intPosition = strpos($strMethodName, ':')) !== false) {
                $strDestControlId = substr($strMethodName, 0, $intPosition);
                $strMethodName = substr($strMethodName, $intPosition + 1);

                $objDestControl = $this->objControlArray[$strDestControlId];
                QControl::_CallActionMethod ($objDestControl, $strMethodName, $this->strFormId, $params);
            } else {
                $this->$strMethodName($this, $params['controlId'], $params['param'], $params);
            }
        }

        /**
         * Calles 'Validate' method on a QControl recursively
         * @param QControl $objControl
         *
         * @return bool
         */
        protected function ValidateControlAndChildren(QControl $objControl) {
            return $objControl->ValidateControlAndChildren();
        }

        /**
         * Runs/Triggers any and all event handling functions for the control on which an event took place
         * Depending on the control's CausesValidation value, it also calls for validation of the control or
         * control and children or entire QForm.
         *
         * @param null|string $strControlIdOverride If supplied, the control with the supplied ID is selected
         *
         * @throws Exception|QCallerException
         */
        protected function TriggerActions($strControlIdOverride = null) {
            if (array_key_exists('Qform__FormControl', $_POST)) {
                if ($strControlIdOverride) {
                    $strControlId = $strControlIdOverride;
                } else {
                    $strControlId = $_POST['Qform__FormControl'];
                }

                // Control ID determined
                if ($strControlId != '') {
                    $strEvent = $_POST['Qform__FormEvent'];
                    $strAjaxActionId = NULL;

                    // Does this Control which performed the action exist?
                    if (array_key_exists($strControlId, $this->objControlArray)) {
                        // Get the ActionControl as well as the Actions to Perform
                        $objActionControl = $this->objControlArray[$strControlId];

                        switch ($this->strCallType) {
                            case QCallType::Ajax:
                                // split up event class name and ajax action id: i.e.: QClickEvent#a3 => [QClickEvent, a3]
                                $arrTemp = explode('#',$strEvent);
                                $strEvent = $arrTemp[0];
                                if(count($arrTemp) == 2)
                                    $strAjaxActionId = $arrTemp[1];
                                $objActions = $objActionControl->GetAllActions($strEvent, 'QAjaxAction');
                                break;
                            case QCallType::Server:
                                $objActions = $objActionControl->GetAllActions($strEvent, 'QServerAction');
                                break;
                            default:
                                throw new Exception('Unknown Form CallType: ' . $this->strCallType);
                        }

                        // Validation Check
                        $blnValid = true;
                        $mixCausesValidation = null;

                        // Figure out what the CausesValidation directive is
                        // Set $mixCausesValidation to the default one (e.g. the one defined on the control)
                        $mixCausesValidation = $objActionControl->CausesValidation;

                        // Next, go through the linked ajax/server actions to see if a causesvalidation override is set on any of them
                        if ($objActions) foreach ($objActions as $objAction) {
                            if (!is_null($objAction->CausesValidationOverride)) {
                                $mixCausesValidation = $objAction->CausesValidationOverride;
                            }
                        }

                        // Now, Do Something with mixCauseValidation...

                        // Starting Point is a QControl
                        if ($mixCausesValidation instanceof QControl) {
                            if (!$this->ValidateControlAndChildren($mixCausesValidation)) {
                                $blnValid = false;
                            }

                            // Starting Point is an Array of QControls
                        } else if (is_array($mixCausesValidation)) {
                            foreach (((array) $mixCausesValidation) as $objControlToValidate) {
                                if (!$this->ValidateControlAndChildren($objControlToValidate)) {
                                    $blnValid = false;
                                }
                            }

                            // Validate All the Controls on the Form
                        } else if ($mixCausesValidation === QCausesValidation::AllControls) {
                            foreach ($this->GetChildControls($this) as $objControl) {
                                // Only Enabled and Visible and Rendered controls that are children of this form should be validated
                                if (($objControl->Visible) && ($objControl->Enabled) && ($objControl->RenderMethod) && ($objControl->OnPage)) {
                                    if (!$this->ValidateControlAndChildren($objControl)) {
                                        $blnValid = false;
                                    }
                                }
                            }

                            // CausesValidation specifed by QCausesValidation directive
                        } else if ($mixCausesValidation == QCausesValidation::SiblingsAndChildren) {
                            // Get only the Siblings of the ActionControl's ParentControl
                            // If not ParentControl, then the parent is the form itself
                            if (!($objParentObject = $objActionControl->ParentControl)) {
                                $objParentObject = $this;
                            }

                            // Get all the children of ParentObject
                            foreach ($this->GetChildControls($objParentObject) as $objControl) {
                                // Only Enabled and Visible and Rendered controls that are children of ParentObject should be validated
                                if (($objControl->Visible) && ($objControl->Enabled) && ($objControl->RenderMethod) && ($objControl->OnPage)) {
                                    if (!$this->ValidateControlAndChildren($objControl)) {
                                        $blnValid = false;
                                    }
                                }
                            }

                            // CausesValidation specifed by QCausesValidation directive
                        } else if ($mixCausesValidation == QCausesValidation::SiblingsOnly) {
                            // Get only the Siblings of the ActionControl's ParentControl
                            // If not ParentControl, tyhen the parent is the form itself
                            if (!($objParentObject = $objActionControl->ParentControl)) {
                                $objParentObject = $this;
                            }

                            // Get all the children of ParentObject
                            foreach ($this->GetChildControls($objParentObject) as $objControl)
                                // Only Enabled and Visible and Rendered controls that are children of ParentObject should be validated
                                if (($objControl->Visible) && ($objControl->Enabled) && ($objControl->RenderMethod) && ($objControl->OnPage)) {
                                    if (!$objControl->Validate()) {
                                        $objControl->MarkAsModified();
                                        $blnValid = false;
                                    }
                                }

                            // No Validation Requested
                        } else {}


                        // Run Form-Specific Validation (if any)
                        if ($mixCausesValidation && !($mixCausesValidation instanceof QDialog)) {
                            if (!$this->Form_Validate()) {
                                $blnValid = false;
                            }
                        }


                        // Go ahead and run the ServerActions or AjaxActions if Validation Passed and if there are Server/Ajax-Actions defined
                        if ($blnValid) {
                            if ($objActions) foreach ($objActions as $objAction) {
                                if ($strMethodName = $objAction->MethodName) {
                                    if (($strAjaxActionId == NULL)             //if this call was not an ajax call
                                        || ($objAction->Id == NULL)         // or the QAjaxAction derived action has no id set
                                        //(a possible way to add a callback that gets executed on every ajax call for this control)
                                        || ($strAjaxActionId == $objAction->Id)) //or the ajax action id passed from client side equals the id of the current ajax action
                                        $this->TriggerMethod($strControlId, $strMethodName, $objAction);
                                }
                            }
                        }
                        else {
                            $this->Form_Invalid();    // notify form that something went wrong
                        }
                    } else {
                        // Nope -- Throw an exception
                        throw new Exception(sprintf('Control passed by Qform__FormControl does not exist: %s', $strControlId));
                    }
                }
                /* else {
                    // TODO: Code to automatically execute any PrimaryButton's onclick action, if applicable
                    // Difficult b/c of all the QCubed hidden parameters that need to be set to get the action to work properly
                    // Javascript interaction of PrimaryButton works fine in Firefox... currently doens't work in IE 6.
                }*/
            }
        }

        /**
         * Begins rendering the page
         */
        protected function Render() {
            if (QWatcher::WatchersChanged()) {
                QApplication::ExecuteJsFunction('qc.broadcastChange');
            }

            require($this->HtmlIncludeFilePath);
        }

        /**
         * Render the children of this QForm
         * @param bool $blnDisplayOutput
         *
         * @return null|string Null when blnDisplayOutput is true
         */
        protected function RenderChildren($blnDisplayOutput = true) {
            $strToReturn = "";

            foreach ($this->GetChildControls($this) as $objControl)
                if (!$objControl->Rendered)
                    $strToReturn .= $objControl->Render($blnDisplayOutput);

            if ($blnDisplayOutput) {
                print($strToReturn);
                return null;
            } else
                return $strToReturn;
        }

        /**
         * This exists to prevent inadverant "New"
         */
        protected function __construct() {}

        /**
         * Renders the tags to include the css style sheets. Call this in your head tag if you want to
         * put these there. Otherwise, the styles will automatically be included just after the form.
         *
         * @param bool $blnDisplayOutput
         * @return null|string
         */
        public function RenderStyles($blnDisplayOutput = true, $blnInHead = true) {
            $strToReturn = '';
            $this->strIncludedStyleSheetFileArray = array();

            // Figure out initial list of StyleSheet includes
            $strStyleSheetArray = array();

            foreach ($this->GetAllControls() as $objControl) {
                // Include any StyleSheets?  The control would have a
                // comma-delimited list of stylesheet files to include (if applicable)
                if ($strScriptArray = $this->ProcessStyleSheetList($objControl->StyleSheets)) {
                    $strStyleSheetArray = array_merge($strStyleSheetArray, $strScriptArray);
                }
            }

            // In order to make ui-themes workable, move the jquery.css to the end of list.
            // It should override any rules that it can override.
            foreach ($strStyleSheetArray as $strScript) {
                if (__JQUERY_CSS__ == $strScript) {
                    unset($strStyleSheetArray[$strScript]);
                    $strStyleSheetArray[$strScript] = $strScript;
                    break;
                }
            }

            // Include styles that need to be included
            foreach ($strStyleSheetArray as $strScript) {
                if ($blnInHead) {
                    $strToReturn  .= '<link href="' . $this->GetCssFileUri($strScript) . '" rel="stylesheet" />';
                } else {
                    $strToReturn  .= '<style type="text/css" media="all">@import "' . $this->GetCssFileUri($strScript) . '"</style>';
                }
                $strToReturn .= "\n";
            }

            self::$blnStylesRendered = true;

            // Return or Display
            if ($blnDisplayOutput) {
                if(!QApplication::$CliMode) {
                    print($strToReturn);
                }
                return null;
            } else {
                if(!QApplication::$CliMode) {
                    return $strToReturn;
                } else {
                    return '';
                }
            }
        }

        /**
         * Initializes the QForm rendering process
         * @param bool $blnDisplayOutput Whether the output is to be printed (true) or simply returned (false)
         *
         * @return null|string
         * @throws QCallerException
         */
        public function RenderBegin($blnDisplayOutput = true) {
            // Ensure that RenderBegin() has not yet been called
            switch ($this->intFormStatus) {
                case QFormBase::FormStatusUnrendered:
                    break;
                case QFormBase::FormStatusRenderBegun:
                case QFormBase::FormStatusRenderEnded:
                    throw new QCallerException('$this->RenderBegin() has already been called');
                    break;
                default:
                    throw new QCallerException('FormStatus is in an unknown status');
            }

            // Update FormStatus and Clear Included JS/CSS list
            $this->intFormStatus = QFormBase::FormStatusRenderBegun;

            // Prepare for rendering

            QApplicationBase::$ProcessOutput = false;
            $strOutputtedText = trim(ob_get_contents());
            if (strpos(strtolower($strOutputtedText), '<body') === false) {
                $strToReturn = '<body>';
                $this->blnRenderedBodyTag = true;
            } else
                $strToReturn = '';
            QApplicationBase::$ProcessOutput = true;


            // Iterate through the form's ControlArray to Define FormAttributes and additional JavaScriptIncludes
            $strFormAttributeArray = array();
            foreach ($this->GetAllControls() as $objControl) {
                // Form Attributes?
                if ($attributes = $objControl->_GetFormAttributes()) {
                    $strFormAttributeArray = array_merge($strFormAttributeArray, $attributes);
                }
            }

            if (is_array($this->strCustomAttributeArray)) {
                $strFormAttributeArray = array_merge($strFormAttributeArray, $this->strCustomAttributeArray);
            }

            if ($this->strCssClass) {
                $strFormAttributeArray['class'] = $this->strCssClass;
            }
            $strFormAttributeArray['method'] = 'post';
            $strFormAttributeArray['id'] = $this->strFormId;
            $strFormAttributeArray['action'] = QApplication::$RequestUri;
            $strToReturn .= '<form ' . QHtml::RenderHtmlAttributes($strFormAttributeArray) . ">\n";

            if (!self::$blnStylesRendered) {
                $strToReturn .= $this->RenderStyles(false, false);
            }

            // Perhaps a strFormModifiers as an array to
            // allow controls to update other parts of the form, like enctype, onsubmit, etc.

            // Return or Display
            if ($blnDisplayOutput) {
                if(!QApplication::$CliMode) {
                    print($strToReturn);
                }
                return null;
            } else {
                if(!QApplication::$CliMode) {
                    return $strToReturn;
                } else {
                    return '';
                }
            }
        }

        /**
         * Internal helper function used by RenderBegin and by RenderAjax
         * Given a comma-delimited list of javascript files, this will return an array of files that NEED to still
         * be included because (1) it hasn't yet been included and (2) it hasn't been specified to be "ignored".
         *
         * This WILL update the internal $strIncludedJavaScriptFileArray array.
         *
         * @param string | array $strJavaScriptFileList
         * @return string[] array of script files to include or NULL if none
         */
        protected function ProcessJavaScriptList($strJavaScriptFileList)  {

            if (empty($strJavaScriptFileList)) return null;

            $strArrayToReturn = array();

            if (!is_array($strJavaScriptFileList)) {
                $strJavaScriptFileList = explode(',', $strJavaScriptFileList);
            }

            // Iterate through the list of JavaScriptFiles to Include...
            foreach ($strJavaScriptFileList as $strScript) {
                if ($strScript = trim($strScript)) {

                    // Include it if we're NOT ignoring it and it has NOT already been included
                    if ((array_search($strScript, $this->strIgnoreJavaScriptFileArray) === false) &&
                        !array_key_exists($strScript, $this->strIncludedJavaScriptFileArray)) {
                        $strArrayToReturn[$strScript] = $strScript;
                        $this->strIncludedJavaScriptFileArray[$strScript] = true;
                    }
                }
            }

            if (count($strArrayToReturn)) {
                return $strArrayToReturn;
            }

            return null;
        }

        /**
         * Primarily used by RenderBegin and by RenderAjax
         * Given a comma-delimited list of stylesheet files, this will return an array of file that NEED to still
         * be included because (1) it hasn't yet been included and (2) it hasn't been specified to be "ignored".
         *
         * This WILL update the internal $strIncludedStyleSheetFileArray array.
         *
         * @param string $strStyleSheetFileList
         * @return string[] array of stylesheet files to include or NULL if none
         */
        protected function ProcessStyleSheetList($strStyleSheetFileList) {
            $strArrayToReturn = array();

            // Is there a comma-delimited list of StyleSheet files to include?
            if ($strStyleSheetFileList = trim($strStyleSheetFileList)) {
                $strScriptArray = explode(',', $strStyleSheetFileList);

                // Iterate through the list of StyleSheetFiles to Include...
                foreach ($strScriptArray as $strScript)
                    if ($strScript = trim($strScript))

                        // Include it if we're NOT ignoring it and it has NOT already been included
                        if ((array_search($strScript, $this->strIgnoreStyleSheetFileArray) === false) &&
                            !array_key_exists($strScript, $this->strIncludedStyleSheetFileArray)) {
                            $strArrayToReturn[$strScript] = $strScript;
                            $this->strIncludedStyleSheetFileArray[$strScript] = true;
                        }
            }

            if (count($strArrayToReturn)) {
                return $strArrayToReturn;
            }

            return null;
        }

        /**
         * Returns whether or not this Form is being run due to a PostBack event (e.g. a ServerAction or AjaxAction)
         * @return bool
         */
        public function IsPostBack() {
            return ($this->strCallType != QCallType::None);
        }

        /**
         * Will return an array of Strings which will show all the error and warning messages
         * in all the controls in the form.
         *
         * @param bool $blnErrorsOnly Show only the errors (otherwise, show both warnings and errors)
         * @return string[] an array of strings representing the (multiple) errors and warnings
         */
        public function GetErrorMessages($blnErrorsOnly = false) {
            $strToReturn = array();
            foreach ($this->GetAllControls() as $objControl) {
                if ($objControl->ValidationError)
                    array_push($strToReturn, $objControl->ValidationError);
                if (!$blnErrorsOnly)
                    if ($objControl->Warning)
                        array_push($strToReturn, $objControl->Warning);
            }

            return $strToReturn;
        }

        /**
         * Will return an array of QControls from the form which have either an error or warning message.
         *
         * @param bool $blnErrorsOnly Return controls that have just errors (otherwise, show both warnings and errors)
         * @return QControl[] an array of controls representing the (multiple) errors and warnings
         */
        public function GetErrorControls($blnErrorsOnly = false) {
            $objToReturn = array();
            foreach ($this->GetAllControls() as $objControl) {
                if ($objControl->ValidationError)
                    array_push($objToReturn, $objControl);
                else if (!$blnErrorsOnly)
                    if ($objControl->Warning)
                        array_push($objToReturn, $objControl);
            }

            return $objToReturn;
        }

        /**
         * Gets the JS file URI, given a string input
         * @param string $strFile File name to be tested
         *
         * @return string the final JS file URI
         */
        public function GetJsFileUri($strFile) {
            return QApplication::GetJsFileUri($strFile);
        }

        /**
         * Gets the CSS file URI, given a string input
         * @param string $strFile File name to be tested
         *
         * @return string the final CSS URI
         */
        public function GetCssFileUri($strFile) {
            return QApplication::GetCssFileUri($strFile);
        }

        /**
         * Get high level form javascript files to be included. Default here includes all
         * javascripts needed to run qcubed.
         * Override and add to this list and include
         * javascript and jQuery files and libraries needed for your application.
         * Javascript files included before __QCUBED_JS_CORE__ can refer to jQuery as $.
         * After qcubed.js, $ becomes $j, so add other libraries that need
         * $ in a different context after qcubed.js, and insert jQuery libraries and  plugins that
         * refer to $ before qcubed.js file.
         *
         * @return array
         */
        protected function GetFormJavaScripts() {
            return array (__JQUERY_BASE__,
                __JQUERY_EFFECTS__,
                'ajaxq/ajaxq.js',
                __QCUBED_JS_CORE__);
        }

        /**
         * Renders the end of the form, including the closing form and body tags.
         * Renders the html for hidden controls.
         * @param bool $blnDisplayOutput should the output be returned or directly printed to screen.
         *
         * @return null|string
         * @throws QCallerException
         */
        public function RenderEnd($blnDisplayOutput = true) {
            // Ensure that RenderEnd() has not yet been called
            switch ($this->intFormStatus) {
                case QFormBase::FormStatusUnrendered:
                    throw new QCallerException('$this->RenderBegin() was never called');
                case QFormBase::FormStatusRenderBegun:
                    break;
                case QFormBase::FormStatusRenderEnded:
                    throw new QCallerException('$this->RenderEnd() has already been called');
                    break;
                default:
                    throw new QCallerException('FormStatus is in an unknown status');
            }

            $strHtml = '';    // This will be the final output

            /**** Render any controls that get automatically rendered ****/
            foreach ($this->GetAllControls() as $objControl) {
                if ($objControl->AutoRender &&
                    !$objControl->Rendered) {
                    $strRenderMethod = $objControl->PreferredRenderMethod;
                    $strHtml .= $objControl->$strRenderMethod(false) . _nl();
                }
            }

            /**** Prepare Javascripts ****/

            // Clear included javascript array since we are completely redrawing the page
            $this->strIncludedJavaScriptFileArray = array();
            $strControlIdToRegister = array();
            $strEventScripts = '';

            // Add form level javascripts and libraries
            $strJavaScriptArray = $this->ProcessJavaScriptList($this->GetFormJavaScripts());
            QApplication::AddJavaScriptFiles($strJavaScriptArray);
            $strFormJsFiles = QApplication::RenderFiles();    // Render the form-level javascript files separately

            // Go through all controls and gather up any JS or CSS to run or Form Attributes to modify
            foreach ($this->GetAllControls() as $objControl) {
                if ($objControl->Rendered || $objControl->ScriptsOnly) {
                    $strControlIdToRegister[] = $objControl->ControlId;

                    /* Note: GetEndScript may cause the control to register additional commands, or even add javascripts, so those should be handled after this. */
                    if ($strControlScript = $objControl->GetEndScript()) {
                        $strControlScript = JavaScriptHelper::TerminateScript($strControlScript);

                        // Add comments for developer version of output
                        if (!QApplication::$Minimize) {
                            // Render a comment
                            $strControlScript = _nl() .  _nl() .
                                sprintf ('/*** EndScript -- Control Type: %s, Control Name: %s, Control Id: %s  ***/',
                                    get_class($objControl), $objControl->Name, $objControl->ControlId) .
                                _nl() .
                                _indent($strControlScript);
                        }
                        $strEventScripts .= $strControlScript;
                    }
                }

                // Include the javascripts specified by each control.
                if ($strScriptArray = $this->ProcessJavaScriptList($objControl->JavaScripts)) {
                    QApplication::AddJavaScriptFiles($strScriptArray);
                }

                // Include any StyleSheets?  The control would have a
                // comma-delimited list of stylesheet files to include (if applicable)
                if ($strScriptArray = $this->ProcessStyleSheetList($objControl->StyleSheets)) {
                    QApplication::AddStyleSheets(array_keys($strScriptArray));
                }
            }

            // Add grouping commands to events (Used for deprecated drag and drop, but not removed yet)
            foreach ($this->objGroupingArray as $objGrouping) {
                $strGroupingScript = $objGrouping->Render();
                if (strlen($strGroupingScript) > 0) {
                    $strGroupingScript = JavaScriptHelper::TerminateScript($strGroupingScript);
                    $strEventScripts .= $strGroupingScript;
                }
            }

            /*** Build the javascript block ****/

            // Start with variable settings and initForm
            $strEndScript = sprintf('qc.initForm("%s"); ', $this->strFormId);

            // Register controls
            if ($strControlIdToRegister) {
                $strEndScript .= sprintf("qc.regCA(%s); \n", JavaScriptHelper::toJsObject($strControlIdToRegister));
            }

            // Design mode event
            if (defined('__DESIGN_MODE__') && __DESIGN_MODE__ == 1) {
                // attach an event listener to the form to send context menu selections to the designer dialog for processing
                $strEndScript .= sprintf(
                    '$j("#%s").on("contextmenu", "[id]", 
                        function(event) {
                            $j("#qconnectoreditdlg").trigger("qdesignerclick", 
                                [{id: event.target.id ? event.target.id : $j(event.target).parents("[id]").attr("id"), for: $j(event.target).attr("for")}]
                            );
                            return false;
                        }
                    );', $this->FormId);
            }

            // Add any application level js commands.
            // This will include high and medimum level commands
            $strEndScript .= QApplication::RenderJavascript(true);

            // Add the javascript coming from controls and events just after the medium level commands
            $strEndScript .=  ';'  . $strEventScripts;

            // Add low level commands and other things that need to execute at the end
            $strEndScript .= ';' . QApplication::RenderJavascript(false);




            // Create Final EndScript Script
            $strEndScript = sprintf('<script type="text/javascript">$j(document).ready(function() { %s; });</script>', $strEndScript);


            /**** Render the HTML itself, appending the javascript we generated above ****/

            foreach ($this->GetAllControls() as $objControl) {
                if ($objControl->Rendered) {
                    $strHtml .= $objControl->GetEndHtml();
                }
                $objControl->ResetFlags(); // Make sure controls are serialized in a reset state
            }

            $strHtml .= $strFormJsFiles . _nl();    // Add form level javascript files

            // put javascript environment defines up early for use by other js files.
            $strHtml .= '<script type="text/javascript">' .
                sprintf('qc.baseDir = "%s"; ', __VIRTUAL_DIRECTORY__) .
                sprintf('qc.jsAssets = "%s"; ', __VIRTUAL_DIRECTORY__ . __JS_ASSETS__) .
                sprintf('qc.phpAssets = "%s"; ', __VIRTUAL_DIRECTORY__ . __PHP_ASSETS__) .
                sprintf('qc.cssAssets = "%s"; ', __VIRTUAL_DIRECTORY__ . __CSS_ASSETS__) .
                sprintf('qc.imageAssets = "%s"; ', __VIRTUAL_DIRECTORY__ . __IMAGE_ASSETS__) .
                '</script>' .
                _nl();

            $strHtml .= QApplication::RenderFiles() . _nl();    // add plugin and control js files

            // Render hidden controls related to the form
            $strHtml .= sprintf('<input type="hidden" name="Qform__FormId" id="Qform__FormId" value="%s" />', $this->strFormId) . _nl();
            $strHtml .= sprintf('<input type="hidden" name="Qform__FormControl" id="Qform__FormControl" value="" />') . _nl();
            $strHtml .= sprintf('<input type="hidden" name="Qform__FormEvent" id="Qform__FormEvent" value="" />') . _nl();
            $strHtml .= sprintf('<input type="hidden" name="Qform__FormParameter" id="Qform__FormParameter" value="" />') . _nl();
            $strHtml .= sprintf('<input type="hidden" name="Qform__FormCallType" id="Qform__FormCallType" value="" />') . _nl();
            $strHtml .= sprintf('<input type="hidden" name="Qform__FormUpdates" id="Qform__FormUpdates" value="" />') . _nl();
            $strHtml .= sprintf('<input type="hidden" name="Qform__FormCheckableControls" id="Qform__FormCheckableControls" value="" />') . _nl();

            // Serialize and write out the formstate
            $strHtml .= sprintf('<input type="hidden" name="Qform__FormState" id="Qform__FormState" value="%s" />', QForm::Serialize(clone($this))) . _nl();

            // close the form tag
            $strHtml .= "</form>";

            // Add the JavaScripts rendered above
            $strHtml .= $strEndScript;

            // close the body tag
            if ($this->blnRenderedBodyTag) {
                $strHtml .= '</body>';
            }

            /**** Cleanup ****/

            // Update Form Status
            $this->intFormStatus = QFormBase::FormStatusRenderEnded;

            // Display or Return
            if ($blnDisplayOutput) {
                if(!QApplication::$CliMode) {
                    print($strHtml);
                }
                return null;
            } else {
                if(!QApplication::$CliMode) {
                    return $strHtml;
                } else {
                    return '';
                }
            }
        }
    }

    function __QForm_EvaluateTemplate_ObHandler($strBuffer) {
        return $strBuffer;
    }