includes/base_controls/QFormBase.class.php
<?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;
}