1: <?php
2: /**
3: * This file contains the QFormBase class.
4: *
5: * @package Controls
6: * @filesource
7: */
8:
9: /**
10: * @package Controls
11: * @property-read string $FormId Form ID of the QForm
12: * @property-read string $CallType Type of call (useful when the QForm submits due to user action)
13: * @property-read QWaitIcon $DefaultWaitIcon Default Ajax wait icon control
14: * @property-read integer $FormStatus Status of form (pre-render stage, rendering stage of already rendered stage)
15: * @property string $HtmlIncludeFilePath (Alternate) path to the template file to be used
16: * @property string $CssClass Form CSS class.
17: */
18: abstract class QFormBase extends QBaseClass {
19: ///////////////////////////
20: // Static Members
21: ///////////////////////////
22: /** @var bool True when css scripts get rendered on page. Lets user call RenderStyles in header. */
23: protected static $blnStylesRendered = false;
24:
25: ///////////////////////////
26: // Protected Member Variables
27: ///////////////////////////
28: /** @var string Form ID (usually passed as the first argument to the 'Run' method call) */
29: protected $strFormId;
30: /** @var integer representational integer value of what state the form currently is in */
31: protected $intFormStatus;
32: /** @var QControl[] Array of QControls with this form as the parent */
33: protected $objControlArray;
34: /**
35: * @var QControlGrouping List of Groupings in the form (for old drag and drop)
36: * Use of this is deprecated in favor of jQueryUI drag and drop, but code remains in case we need it again.
37: * @deprecated
38: */
39: protected $objGroupingArray;
40: /** @var bool Has the body tag already been rendered? */
41: protected $blnRenderedBodyTag = false;
42: protected $checkableControlValues = array();
43: /** @var string The type of call made to the QForm (Ajax, Server or Fresh GET request) */
44: protected $strCallType;
45: /** @var null|QWaitIcon Default wait icon for the page/QForm */
46: protected $objDefaultWaitIcon = null;
47:
48: protected $strFormAttributeArray = array();
49:
50: /** @var array List of included JavaScript files for this QForm */
51: protected $strIncludedJavaScriptFileArray = array();
52: /** @var array List of ignored JavaScript files for this QForm */
53: protected $strIgnoreJavaScriptFileArray = array();
54:
55: /** @var array List of included CSS files for this QForm */
56: protected $strIncludedStyleSheetFileArray = array();
57: /** @var array List of ignored CSS files for this QForm */
58: protected $strIgnoreStyleSheetFileArray = array();
59:
60: protected $strPreviousRequestMode = false;
61: /**
62: * @var string The QForm's template file path.
63: * When this value is not supplied, the 'Run' function will try to find and use the
64: * .tpl.php file with the same filename as the QForm in the same same directory as the QForm file.
65: */
66: protected $strHtmlIncludeFilePath;
67: /** @var string CSS class to be set for the 'form' tag when QCubed Renders the QForm */
68: protected $strCssClass;
69:
70: protected $strCustomAttributeArray = null;
71:
72: ///////////////////////////
73: // Form Status Constants
74: ///////////////////////////
75: /** Form has not started rendering */
76: const FormStatusUnrendered = 1;
77: /** Form has started rendering but has not finished */
78: const FormStatusRenderBegun = 2;
79: /** Form rendering has already been started and finished */
80: const FormStatusRenderEnded = 3;
81:
82: ///////////////////////////
83: // Form Preferences
84: ///////////////////////////
85: /**
86: * @var null|string The key to encrypt the formstate
87: * when saving and retrieving from the chosen FormState handler
88: */
89: public static $EncryptionKey = null;
90: /**
91: * @var string Chosen FormStateHandler
92: * default is QFormStateHandler as shown here,
93: * however it is read from the configuration.inc.php (in the QForm class)
94: * In case something goes wrong with QForm, the default FormStateHandler here will
95: * try to take care of the situation.
96: */
97: public static $FormStateHandler = 'QFormStateHandler';
98:
99: /////////////////////////
100: // Public Properties: GET
101: /////////////////////////
102: /**
103: * PHP magic method for getting property values of object
104: * @param string $strName Name of the propery
105: *
106: * @return int|mixed|null|string
107: * @throws QCallerException
108: */
109: public function __get($strName) {
110: switch ($strName) {
111: case "FormId": return $this->strFormId;
112: case "CallType": return $this->strCallType;
113: case "DefaultWaitIcon": return $this->objDefaultWaitIcon;
114: case "FormStatus": return $this->intFormStatus;
115: case "HtmlIncludeFilePath": return $this->strHtmlIncludeFilePath;
116: case "CssClass": return $this->strCssClass;
117:
118: default:
119: try {
120: return parent::__get($strName);
121: } catch (QCallerException $objExc) {
122: $objExc->IncrementOffset();
123: throw $objExc;
124: }
125: }
126: }
127:
128: /////////////////////////
129: // Public Properties: SET
130: /////////////////////////
131: /**
132: * PHP magic function to set the value of properties of class object
133: * @param string $strName Name of the property
134: * @param string $mixValue Value of the property
135: *
136: * @return mixed|string
137: * @throws QCallerException
138: */
139: public function __set($strName, $mixValue) {
140: switch ($strName) {
141: case "HtmlIncludeFilePath":
142: // Passed-in value is null -- use the "default" path name of file".tpl.php"
143: if (!$mixValue) {
144: $strPath = realpath(substr(QApplication::$ScriptFilename, 0, strrpos(QApplication::$ScriptFilename, '.php')) . '.tpl.php');
145: if ($strPath === false) {
146: // Look again based on the object name
147: $strPath = realpath(get_class($this) . '.tpl.php');
148: }
149: }
150:
151: // Use passed-in value
152: else
153: $strPath = realpath($mixValue);
154:
155: // Verify File Exists, and if not, throw exception
156: if (is_file($strPath)) {
157: $this->strHtmlIncludeFilePath = $strPath;
158: return $strPath;
159: } else
160: throw new QCallerException('Accompanying HTML Include File does not exist: "' . $mixValue . '"');
161: break;
162:
163: case "CssClass":
164: try {
165: return ($this->strCssClass = QType::Cast($mixValue, QType::String));
166: } catch (QCallerException $objExc) {
167: $objExc->IncrementOffset();
168: throw $objExc;
169: }
170:
171: default:
172: try {
173: return parent::__set($strName, $mixValue);
174: } catch (QCallerException $objExc) {
175: $objExc->IncrementOffset();
176: throw $objExc;
177: }
178: }
179: }
180:
181:
182: /////////////////////////
183: // Helpers for ControlId Generation
184: /////////////////////////
185:
186: /**
187: * Generates Control ID used to keep track of those QControls whose ID was not explicitly set.
188: * It uses the counter variable to maintain uniqueness for Control IDs during the life of the page
189: * Life of the page is untill the time when the formstate expired and is removed by the
190: * garbage collection of the formstate handler
191: * @return string the Ajax Action ID
192: */
193: public function GenerateControlId() {
194: // $strToReturn = sprintf('control%s', $this->intNextControlId);
195: $strToReturn = sprintf('c%s', $this->intNextControlId);
196: $this->intNextControlId++;
197: return $strToReturn;
198: }
199: /**
200: * @var int Counter variable to contain the numerical part of the Control ID value.
201: * it is automatically incremented everytime the GenerateControlId() runs
202: */
203: protected $intNextControlId = 1;
204:
205: /////////////////////////
206: // Helpers for AjaxActionId Generation
207: /////////////////////////
208: /**
209: * Generates Ajax Action ID used to keep track of Ajax Actions
210: * It uses the counter variable to maintain uniqueness for Ajax Action IDs during the life of the page
211: * Life of the page is untill the time when the formstate expired and is removed by the
212: * garbage collection of the formstate handler
213: * @return string the Ajax Action ID
214: */
215: public function GenerateAjaxActionId() {
216: $strToReturn = sprintf('a%s', $this->intNextAjaxActionId);
217: $this->intNextAjaxActionId++;
218: return $strToReturn;
219: }
220:
221: /**
222: * @var int Counter variable to contain the numerical part of the AJAX ID value.
223: * it is automatically incremented everytime the GenerateAjaxActionId() runs
224: */
225: protected $intNextAjaxActionId = 1;
226:
227: /////////////////////////
228: // Event Handlers
229: /////////////////////////
230: /**
231: * Custom Form Run code.
232: * To contain code which should be run 'AFTER' QCubed's QForm run has been completed
233: * but 'BEFORE' the custom event handlers are called
234: * (In case it is to be used, it should be overriden by a child class)
235: */
236: protected function Form_Run() {}
237:
238: /**
239: * To contain the code which should be executed after the Form Run and
240: * before the custom handlers are called (In case it is to be used, it should be overridden by a child class)
241: * In this situation, we are about to process an event, or the user has reloaded the page. Do whatever you
242: * need to do before any event processing.
243: */
244: protected function Form_Load() {}
245:
246: /**
247: * To contain the code to initialize the QForm on the first call.
248: * Once the QForm is created, the state is saved and is reused by the Run method.
249: * In short - this function will run only once (the first time the QForm is to be created)
250: * (In case it is to be used, it should be overriden by a child class)
251: */
252: protected function Form_Create() {}
253:
254: /**
255: * To contain the code to be executed after Form_Run, Form_Create, Form_Load has been called
256: * and the custom defined event handlers have been executed but actual rendering process has not begun.
257: * This is a good place to put data into a session variable that you need to send to
258: * other forms.
259: */
260: protected function Form_PreRender() {}
261:
262: /**
263: * Override this method to set data in your form controls. Appropriate things to do would be to:
264: * - Respond to options sent by _GET or _POST variables.
265: * - Load data into the control from the database
266: * - Initialize controls whose data depends on the state or data in other controls.
267: *
268: * When this is called, the controls will have been created by Form_Create, and will have already read their saved state.
269: *
270: */
271: protected function Form_Initialize() {}
272:
273: /**
274: * The Form_Validate method.
275: *
276: * Before we get here, all the controls will first be validated. Override this method to do
277: * additional form level validation, and any form level actions needed as part of the validation process,
278: * like displaying an error message.
279: *
280: * This is the last thing called in the validation process, and will always be called if
281: * validation is requested, even if prior controls caused a validation error. Return false to prevent
282: * validation and cancel the current action.
283: *
284: * $blnValid will contain the result of control validation. If it is false, you know that validation will
285: * fail, regardless of what you return from the function.
286: *
287: * @return bool Return false to prevent validation.
288: */
289: protected function Form_Validate() {return true;}
290:
291: /**
292: * If you want to respond in some way to an invalid form that you have not already been able to handle,
293: * override this function. For example, you could display a message that an error occurred with some of the
294: * controls.
295: */
296: protected function Form_Invalid() {}
297:
298: /**
299: * This function is meant to be overriden by child class and is called when the Form exits
300: * (After the form render is complete and just before the Run function completes execution)
301: */
302: protected function Form_Exit() {}
303:
304:
305: /**
306: * VarExport the Controls or var_export the current QForm
307: * (well, be ready for huge amount of text)
308: * @param bool $blnReturn
309: *
310: * @return mixed
311: */
312: public function VarExport($blnReturn = true) {
313: if ($this->objControlArray) foreach ($this->objControlArray as $objControl)
314: $objControl->VarExport(false);
315: if ($blnReturn) {
316: return var_export($this, true);
317: }
318: else {
319: return null;
320: }
321: }
322:
323: /**
324: * Returns the value of a checkable control. Checkable controls are special, in that the browser only tells us
325: * when a control is checked, not when it is unchecked. So, unless we keep track of them specially, we will
326: * not know if they are unchecked, or just not there.
327: * @param $strControlId
328: * @return mixed|null
329: */
330: public function CheckableControlValue($strControlId) {
331: if (array_key_exists($strControlId, $this->checkableControlValues)) {
332: return $this->checkableControlValues[$strControlId];
333: }
334: return null;
335: }
336:
337:
338: /**
339: * Helper function for below GetModifiedControls
340: * @param QControl $objControl
341: * @return boolean
342: */
343: protected static function IsControlModified ($objControl) {
344: return $objControl->IsModified();
345: }
346: /**
347: * Return only the controls that have been modified
348: */
349: public function GetModifiedControls() {
350: $ret = array_filter ($this->objControlArray, 'QForm::IsControlModified');
351: return $ret;
352: }
353:
354: /**
355: * This method initializes the actual layout of the form
356: * It runs in all cases including initial form (the time when Form_Create is run) as well as on
357: * trigger actions (QServerAction, QAjaxAction, QServerControlAction and QAjaxControlAction)
358: *
359: * It is responsible for implementing the logic and sequence in which page wide checks are done
360: * such as running Form_Validate and Control validations for every control of the page and their
361: * child controls. Checking for an existing FormState and loading them before trigerring any action
362: * is also a responsibility of this method.
363: * @param string $strFormClass The class of the form to create when creating a new form.
364: * @param string|null $strAlternateHtmlFile location of the alternate HTML template file.
365: * @param string|null $strFormId The html id to use for the form. If null, $strFormClass will be used.
366: *
367: * @throws QCallerException
368: * @throws QInvalidFormStateException
369: * @throws Exception
370: */
371: public static function Run($strFormClass, $strAlternateHtmlFile = null, $strFormId = null) {
372: // See if we can get a Form Class out of PostData
373: $objClass = null;
374: if ($strFormId === null) {
375: $strFormId = $strFormClass;
376: }
377: if (array_key_exists('Qform__FormId', $_POST) && ($_POST['Qform__FormId'] == $strFormId) && array_key_exists('Qform__FormState', $_POST)) {
378: $strPostDataState = $_POST['Qform__FormState'];
379:
380: if ($strPostDataState)
381: // We might have a valid form state -- let's see by unserializing this object
382: $objClass = QForm::Unserialize($strPostDataState);
383:
384: // If there is no QForm Class, then we have an Invalid Form State
385: if (!$objClass) {
386: self::InvalidFormState();
387: }
388: }
389:
390: if ($objClass) {
391: // Globalize
392: global $_FORM;
393: $_FORM = $objClass;
394:
395: $objClass->strCallType = $_POST['Qform__FormCallType'];
396: $objClass->intFormStatus = QFormBase::FormStatusUnrendered;
397:
398: if ($objClass->strCallType == QCallType::Ajax) {
399: QApplication::$RequestMode = QRequestMode::Ajax;
400: }
401:
402: // Cleanup ajax post data if the encoding does not match, since ajax data is always utf-8
403: if ($objClass->strCallType == QCallType::Ajax && QApplication::$EncodingType != 'UTF-8') {
404: foreach ($_POST as $key=>$val) {
405: if (substr($key, 0, 6) != 'Qform_') {
406: $_POST[$key] = iconv('UTF-8', QApplication::$EncodingType, $val);
407: }
408: }
409: }
410:
411: if (!empty($_POST['Qform__FormParameter'])) {
412: $_POST['Qform__FormParameter'] = self::UnpackPostVar($_POST['Qform__FormParameter']);
413: }
414:
415: // Decode custom post variables from server calls
416: if (!empty($_POST['Qform__AdditionalPostVars'])) {
417: $val = self::UnpackPostVar($_POST['Qform__AdditionalPostVars']);
418: $_POST = array_merge($_POST, $val);
419: }
420:
421: // Iterate through all the control modifications
422: if (!empty($_POST['Qform__FormUpdates'])) {
423: $controlUpdates = $_POST['Qform__FormUpdates'];
424: if (is_string($controlUpdates)) { // Server post is encoded, ajax not encoded
425: $controlUpdates = self::UnpackPostVar($controlUpdates);
426: }
427: if (!empty($controlUpdates)) {
428: foreach ($controlUpdates as $strControlId=>$params) {
429: foreach ($params as $strProperty=>$strValue) {
430: switch ($strProperty) {
431: case 'Parent':
432: if ($strValue) {
433: if ($strValue == $objClass->FormId) {
434: $objClass->objControlArray[$strControlId]->SetParentControl(null);
435: } else {
436: $objClass->objControlArray[$strControlId]->SetParentControl($objClass->objControlArray[$strValue]);
437: }
438: } else {
439: // Remove all parents
440: $objClass->objControlArray[$strControlId]->SetParentControl(null);
441: $objClass->objControlArray[$strControlId]->SetForm(null);
442: $objClass->objControlArray[$strControlId] = null;
443: unset($objClass->objControlArray[$strControlId]);
444: }
445: break;
446: default:
447: if (array_key_exists($strControlId, $objClass->objControlArray))
448: $objClass->objControlArray[$strControlId]->__set($strProperty, $strValue);
449: break;
450:
451: }
452: }
453: }
454: }
455: }
456:
457:
458: // Set the RenderedCheckableControlArray
459: if (!empty($_POST['Qform__FormCheckableControls'])) {
460: $vals = $_POST['Qform__FormCheckableControls'];
461: if (is_string($vals)) { // Server post is encoded, ajax not encoded
462: $vals = self::UnpackPostVar($vals);
463: }
464: $objClass->checkableControlValues = $vals;
465: } else {
466: $objClass->checkableControlValues = [];
467: }
468:
469: // This is original code. In an effort to minimize changes,
470: // we aren't going to touch the server calls for now
471: if ($objClass->strCallType != QCallType::Ajax) {
472: foreach ($objClass->objControlArray as $objControl) {
473: // If they were rendered last time and are visible
474: // (and if ServerAction, enabled), then Parse its post data
475: if (($objControl->Visible) &&
476: ($objControl->Enabled) &&
477: ($objControl->RenderMethod)) {
478: // Call each control's ParsePostData()
479: $objControl->ParsePostData();
480: }
481:
482: // Reset the modified/rendered flags and the validation
483: // in ALL controls
484: $objControl->ResetFlags();
485: }
486: }
487: else {
488: // Ajax post. Only send data to controls specified in the post to save time.
489:
490: $previouslyFoundArray = array();
491: $controls = $_POST;
492: $controls = array_merge($controls, $objClass->checkableControlValues);
493: foreach ($controls as $key=>$val) {
494: if ($key == 'Qform__FormControl') {
495: $strControlId = $val;
496: } elseif (substr($key, 0, 6) == 'Qform_') {
497: continue; // ignore this form data
498: } else {
499: $strControlId = $key;
500: }
501: if (($intOffset = strpos ($strControlId, '_')) !== false) { // the first break is the control id
502: $strControlId = substr ($strControlId, 0, $intOffset);
503: }
504: if (($objControl = $objClass->GetControl($strControlId)) &&
505: !isset($previouslyFoundArray[$strControlId])) {
506: if (($objControl->Visible) &&
507: ($objControl->RenderMethod)) {
508: // Call each control's ParsePostData()
509: $objControl->ParsePostData();
510: }
511:
512: $previouslyFoundArray[$strControlId] = true;
513: }
514: }
515: }
516:
517: // Only if our action is validating, we are going to reset the validation state of all the controls
518: if (isset($_POST['Qform__FormControl']) && isset($objClass->objControlArray[$_POST['Qform__FormControl']])) {
519: $objControl = $objClass->objControlArray[$_POST['Qform__FormControl']];
520: if ($objControl->CausesValidation) {
521: $objClass->ResetValidationStates();
522: }
523: }
524:
525: // Trigger Run Event (if applicable)
526: $objClass->Form_Run();
527:
528: // Trigger Load Event (if applicable)
529: $objClass->Form_Load();
530:
531: // Trigger a triggered control's Server- or Ajax- action (e.g. PHP method) here (if applicable)
532: $objClass->TriggerActions();
533:
534: } else {
535: // We have no form state -- Create Brand New One
536: $objClass = new $strFormClass();
537:
538: // Globalize
539: global $_FORM;
540: $_FORM = $objClass;
541:
542: // Setup HTML Include File Path, based on passed-in strAlternateHtmlFile (if any)
543: try {
544: $objClass->HtmlIncludeFilePath = $strAlternateHtmlFile;
545: } catch (QCallerException $objExc) {
546: $objExc->IncrementOffset();
547: throw $objExc;
548: }
549:
550: // By default, this form is being created NOT via a PostBack
551: // So there is no CallType
552: $objClass->strCallType = QCallType::None;
553:
554: $objClass->strFormId = $strFormId;
555: $objClass->intFormStatus = QFormBase::FormStatusUnrendered;
556: $objClass->objControlArray = array();
557: $objClass->objGroupingArray = array();
558:
559: // Trigger Run Event (if applicable)
560: $objClass->Form_Run();
561:
562: // Trigger Create Event (if applicable)
563: $objClass->Form_Create();
564:
565: $objClass->Form_Initialize();
566:
567: if (defined ('__DESIGN_MODE__') && __DESIGN_MODE__ == 1) {
568: // Attach custom event to dialog to handle right click menu items sent by form
569:
570: $dlg = new QModelConnectorEditDlg ($objClass, 'qconnectoreditdlg');
571:
572: $dlg->AddAction (
573: new QOnEvent('qdesignerclick'),
574: new QAjaxAction ('ctlDesigner_Click', null, null, 'ui')
575: );
576: }
577:
578: }
579:
580: // Trigger PreRender Event (if applicable)
581: $objClass->Form_PreRender();
582:
583: // Render the Page
584: switch ($objClass->strCallType) {
585: case QCallType::Ajax:
586: // Must use AJAX-based renderer
587: $objClass->RenderAjax();
588: break;
589:
590: case QCallType::Server:
591: case QCallType::None:
592: case '':
593: // Server/Postback or New Page
594: // Make sure all controls are marked as not being on the page yet
595: foreach ($objClass->objControlArray as $objControl)
596: $objControl->ResetOnPageStatus();
597:
598: // Use Standard Rendering
599: $objClass->Render();
600:
601: // Ensure that RenderEnd() was called during the Render process
602: switch ($objClass->intFormStatus) {
603: case QFormBase::FormStatusUnrendered:
604: throw new QCallerException('$this->RenderBegin() is never called in the HTML Include file');
605: case QFormBase::FormStatusRenderBegun:
606: throw new QCallerException('$this->RenderEnd() is never called in the HTML Include file');
607: case QFormBase::FormStatusRenderEnded:
608: break;
609: default:
610: throw new QCallerException('FormStatus is in an unknown status');
611: }
612: break;
613:
614: default:
615: throw new Exception('Unknown Form CallType: ' . $objClass->strCallType);
616: }
617:
618: // Once all the controls have been set up, and initialized, remember them.
619: $objClass->SaveControlState();
620:
621: // Tigger Exit Event (if applicable)
622: $objClass->Form_Exit();
623: }
624:
625: /**
626: * Unpacks a post variable that has been encoded with JSON.stringify.
627: *
628: * @param $val
629: * @return mixed|string
630: */
631: protected static function UnpackPostVar($val) {
632: if (QApplication::$EncodingType != 'UTF-8' && QApplication::$RequestMode != QRequestMode::Ajax) {
633: // json_decode only accepts utf-8 encoded text. Ajax calls are already UTF-8 encoded.
634: $val = iconv(QApplication::$EncodingType, 'UTF-8', $val);
635: }
636: $val = json_decode($val, true);
637: if (QApplication::$EncodingType != 'UTF-8') {
638: // Must convert back from utf-8 to whatever our application encoding is
639: if (is_string($val)) {
640: $val = iconv('UTF-8', QApplication::$EncodingType, $val);
641: }
642: elseif (is_array($val)) {
643: array_walk_recursive($val, function(&$v, $key) {
644: if (is_string($v)) {
645: $v = iconv('UTF-8', QApplication::$EncodingType, $v);
646: }
647: });
648: }
649: else {
650: throw new Exception ('Unknown Post Var Type');
651: }
652: }
653: return $val;
654: }
655:
656: /**
657: * Reset all validation states.
658: */
659: public function ResetValidationStates() {
660: foreach ($this->objControlArray as $objControl) {
661: $objControl->ValidationReset();
662: }
663: }
664:
665: /**
666: * Private function to respond to a designer click.
667: *
668: * @param $strFormId
669: * @param $strControlId
670: * @param $mixParam
671: */
672: private function ctlDesigner_Click ($strFormId, $strControlId, $mixParam) {
673: if (isset($mixParam['id'])) {
674: $controlId = $mixParam['id'];
675: if (strpos($controlId, '_')) { // extra the real control id from a sub id
676: $controlId = substr($controlId, 0, strpos($controlId, '_'));
677: }
678: }
679: elseif (isset($mixParam['for'])) {
680: $controlId = $mixParam['for'];
681: }
682: if (!empty($controlId)) {
683: $objControl = $this->GetControl($controlId);
684: if ($objControl) {
685: $dlg = $this->GetControl ('qconnectoreditdlg');
686: $dlg->EditControl ($objControl);
687: }
688: }
689: }
690:
691: /**
692: * An invalid form state was found.
693: * We were handed a formstate, but the formstate could not be interpreted. This could be for
694: * a variety of reasons, and is dependent on the formstate handler. Most likely, the user hit
695: * the back button past the back button limit of what we remember, or the user lost the session.
696: * Or, you simply have not set up the form state handler correctly.
697: * In the past, we threw an exception, but that was not a very user friendly response.
698: * The response below resubmits the url without a formstate so that a new one will be created.
699: * Override if you want a different response.
700: */
701: public static function InvalidFormState() {
702: //ob_clean();
703: if (isset($_POST['Qform__FormCallType']) && $_POST['Qform__FormCallType'] == QCallType::Ajax) {
704: QApplication::$ProcessOutput = false;
705: QApplication::SendAjaxResponse(['loc' => 'reload']);
706: } else {
707: header('Location: '. QApplication::$RequestUri);
708: }
709:
710: // End the Response Script
711: exit();
712: }
713:
714: /**
715: * Calls a data binder associated with the form. Does this so data binder can be protected. Mostly for legacy code.
716: * @param callable $callable
717: * @param QControl $objPaginatedControl
718: * @throws QDataBindException
719: */
720: public function CallDataBinder($callable, $objPaginatedControl) {
721: try {
722: call_user_func($callable, $objPaginatedControl);
723: } catch (QCallerException $objExc) {
724: throw new QDataBindException($objExc);
725: }
726: }
727:
728: /**
729: * Renders the AjaxHelper for the QForm
730: * @param QControlBase $objControl
731: *
732: * @return string The Ajax helper string (should be JS commands)
733: */
734: protected function RenderAjaxHelper($objControl) {
735: $controls = [];
736:
737: if ($objControl) {
738: $controls = array_merge($controls, $objControl->RenderAjax()); // will return an array of controls to be merged with current controls
739: foreach ($objControl->GetChildControls() as $objChildControl) {
740: $controls = array_merge($controls, $this->RenderAjaxHelper($objChildControl));
741: }
742: }
743:
744: return $controls;
745: }
746:
747: /**
748: * Renders the actual ajax return value as a json object. Since json must be UTF-8 encoded, will convert to
749: * UTF-8 if needed. Response is parsed in the "success" function in qcubed.js, and handled there.
750: */
751: protected function RenderAjax() {
752: $aResponse = array();
753:
754: if (QApplication::$JavascriptExclusiveCommand) {
755: /**
756: * Processing of the actions has resulted in a very high priority exclusive response. This would typically
757: * happen when a javascript widget is requesting data from us. We want to respond as quickly as possible,
758: * and also prevent possibly redrawing the widget while its already in the middle of its own drawing.
759: * We short-circuit the drawing process here.
760: */
761:
762: $aResponse = QApplication::GetJavascriptCommandArray();
763: $strFormState = QForm::Serialize($this);
764: $aResponse[QAjaxResponse::Controls][] = [QAjaxResponse::Id=>"Qform__FormState", QAjaxResponse::Value=>$strFormState]; // bring it back next time
765: ob_clean();
766: QApplication::SendAjaxResponse($aResponse);
767: return;
768: }
769:
770: // Update the Status
771: $this->intFormStatus = QFormBase::FormStatusRenderBegun;
772:
773: // Broadcast the watcher change to other windows listening
774: if (QWatcher::WatchersChanged()) {
775: $aResponse[QAjaxResponse::Watcher] = true;
776: }
777:
778: // Recursively render changed controls, starting with all top-level controls
779: $controls = array();
780: foreach ($this->GetAllControls() as $objControl) {
781: if (!$objControl->ParentControl) {
782: $controls = array_merge($controls, $this->RenderAjaxHelper($objControl));
783: }
784: }
785: $aResponse[QAjaxResponse::Controls] = $controls;
786:
787: // Go through all controls and gather up any JS or CSS to run or Form Attributes to modify
788: foreach ($this->GetAllControls() as $objControl) {
789: // Include any javascript files that were added by the control
790: // Note: current implementation does not handle removal of javascript files
791: if ($strScriptArray = $this->ProcessJavaScriptList($objControl->JavaScripts)) {
792: QApplication::AddJavaScriptFiles($strScriptArray);
793: }
794:
795: // Include any new stylesheets
796: if ($strScriptArray = $this->ProcessStyleSheetList($objControl->StyleSheets)) {
797: QApplication::AddStyleSheets(array_keys($strScriptArray));
798: }
799:
800: // Form Attributes?
801: if ($objControl->FormAttributes) {
802: QApplication::ExecuteControlCommand($this->strFormId, 'attr', $objControl->FormAttributes);
803: foreach ($objControl->FormAttributes as $strKey=>$strValue) {
804: if (!array_key_exists($strKey, $this->strFormAttributeArray)) {
805: $this->strFormAttributeArray[$strKey] = $strValue;
806: } else if ($this->strFormAttributeArray[$strKey] != $strValue) {
807: $this->strFormAttributeArray[$strKey] = $strValue;
808: }
809: }
810: }
811: }
812:
813: $strControlIdToRegister = array();
814: foreach ($this->GetAllControls() as $objControl) {
815: $strScript = '';
816: if ($objControl->Rendered) { // whole control was rendered during this event
817: $strScript = trim ($objControl->GetEndScript());
818: $strControlIdToRegister[] = $objControl->ControlId;
819: } else {
820: $objControl->RenderAttributeScripts(); // render one-time attribute commands only
821: }
822: if ($strScript) {
823: QApplication::ExecuteJavaScript($strScript, QJsPriority::High); // put these last in the high priority queue, just before getting the commands below
824: }
825: $objControl->ResetFlags();
826: }
827:
828: if ($strControlIdToRegister) {
829: $aResponse[QAjaxResponse::RegC] = $strControlIdToRegister;
830: }
831:
832:
833: foreach ($this->objGroupingArray as $objGrouping) {
834: $strRender = $objGrouping->Render();
835: if (trim($strRender))
836: QApplication::ExecuteJavaScript($strRender, QJsPriority::High);
837: }
838:
839:
840: $aResponse = array_merge($aResponse, QApplication::GetJavascriptCommandArray());
841:
842: // Add in the form state
843: $strFormState = QForm::Serialize($this);
844: $aResponse[QAjaxResponse::Controls][] = [QAjaxResponse::Id=>"Qform__FormState", QAjaxResponse::Value=>$strFormState];
845:
846: $strContents = trim(ob_get_contents());
847:
848: if (strtolower(substr($strContents, 0, 5)) == 'debug') {
849: // TODO: Output debugging information.
850: } else {
851: ob_clean();
852:
853: QApplication::SendAjaxResponse($aResponse);
854: }
855:
856: // Update Render State
857: $this->intFormStatus = QFormBase::FormStatusRenderEnded;
858: }
859:
860: /**
861: * Saves the formstate using the 'Save' method of FormStateHandler set in configuration.inc.php
862: * @param QForm $objForm
863: *
864: * @return string the Serialized QForm
865: */
866: public static function Serialize(QForm $objForm) {
867: // Get and then Update PreviousRequestMode
868: $strPreviousRequestMode = $objForm->strPreviousRequestMode;
869: $objForm->strPreviousRequestMode = QApplication::$RequestMode;
870:
871: // Figure Out if we need to store state for back-button purposes
872: $blnBackButtonFlag = true;
873: if ($strPreviousRequestMode == QRequestMode::Ajax)
874: $blnBackButtonFlag = false;
875:
876: // Create a Clone of the Form to Serialize
877: $objForm = clone($objForm);
878:
879: // Cleanup internal links between controls and the form
880: if ($objForm->objControlArray) foreach ($objForm->objControlArray as $objControl) {
881: $objControl->Sleep();
882: }
883:
884: // Use PHP "serialize" to serialize the form
885: $strSerializedForm = serialize($objForm);
886:
887: // Setup and Call the FormStateHandler to retrieve the PostDataState to return
888: $strFormStateHandler = QForm::$FormStateHandler;
889: $strPostDataState = $strFormStateHandler::Save ($strSerializedForm, $blnBackButtonFlag);
890:
891: // Return the PostDataState
892: return $strPostDataState;
893: }
894:
895: /**
896: * Unserializes (extracts) the FormState using the 'Load' method of FormStateHandler set in configuration.inc.php
897: * @param string $strPostDataState The string identifying the FormState to the loaded for Unserialization
898: *
899: * @internal param string $strSerializedForm
900: * @return QForm the Form object
901: */
902: public static function Unserialize($strPostDataState) {
903: // Setup and Call the FormStateHandler to retrieve the Serialized Form
904: $strFormStateHandler = QForm::$FormStateHandler;
905: $strSerializedForm = $strFormStateHandler::Load ($strPostDataState);
906:
907: if ($strSerializedForm) {
908: // Unserialize and Cast the Form
909: // For the QSessionFormStateHandler the __PHP_Incomplete_Class occurs sometimes
910: // for the result of the unserialize call.
911: $objForm = unserialize($strSerializedForm);
912: $objForm = QType::Cast($objForm, 'QForm');
913:
914: // Reset the links from Control->Form
915: if ($objForm->objControlArray) foreach ($objForm->objControlArray as $objControl) {
916: // If you are having trouble with a __PHP_Incomplete_Class here, it means you are not including the definitions
917: // of your own controls in the form.
918: $objControl->Wakeup($objForm);
919: }
920:
921: // Return the Form
922: return $objForm;
923: } else
924: return null;
925: }
926:
927: /**
928: * Add a QControl to the current QForm.
929: * @param QControl|QControlBase $objControl
930: *
931: * @throws QCallerException
932: */
933: public function AddControl(QControl $objControl) {
934: $strControlId = $objControl->ControlId;
935: $objControl->MarkAsModified(); // make sure new controls get drawn
936: if (array_key_exists($strControlId, $this->objControlArray))
937: throw new QCallerException(sprintf('A control already exists in the form with the ID: %s', $strControlId));
938: if (array_key_exists($strControlId, $this->objGroupingArray))
939: throw new QCallerException(sprintf('A Grouping already exists in the form with the ID: %s', $strControlId));
940: $this->objControlArray[$strControlId] = $objControl;
941: }
942:
943: /**
944: * Returns a control from the current QForm
945: * @param string $strControlId The Control ID of the control which is needed to be fetched
946: * from the current QForm (should be the child of the current QForm).
947: *
948: * @return null|QControl
949: */
950: public function GetControl($strControlId) {
951: if (isset($this->objControlArray[$strControlId])) {
952: return $this->objControlArray[$strControlId];
953: }
954: else {
955: return null;
956: }
957: }
958:
959: /**
960: * Removes a QControl (and its children) from the current QForm
961: * @param string $strControlId
962: */
963: public function RemoveControl($strControlId) {
964: if (isset($this->objControlArray[$strControlId])) {
965: // Get the Control in Question
966: $objControl = $this->objControlArray[$strControlId];
967:
968: // Remove all Child Controls as well
969: $objControl->RemoveChildControls(true);
970:
971: // Remove this control from the parent
972: if ($objControl->ParentControl)
973: $objControl->ParentControl->RemoveChildControl($strControlId, false);
974:
975: // Remove this control
976: unset($this->objControlArray[$strControlId]);
977:
978: // Remove this control from any groups
979: foreach ($this->objGroupingArray as $strKey => $objGrouping)
980: $this->objGroupingArray[$strKey]->RemoveControl($strControlId);
981: }
982: }
983:
984: /**
985: * Returns all controls belonging to the Form as an array.
986: * @return mixed|QControl[]
987: */
988: public function GetAllControls() {
989: return $this->objControlArray;
990: }
991:
992: /**
993: * Tell all the controls to save their state.
994: */
995: public function SaveControlState() {
996: // tell the controls to save their state
997: $a = $this->GetAllControls();
998: foreach ($a as $control) {
999: $control->_WriteState();
1000: }
1001: }
1002:
1003: /**
1004: * Tell all the controls to read their state.
1005: */
1006: protected function RestoreControlState() {
1007: // tell the controls to restore their state
1008: $a = $this->GetAllControls();
1009: foreach ($a as $control) {
1010: $control->_ReadState();
1011: }
1012: }
1013:
1014:
1015: /**
1016: * Custom Attributes are other html name-value pairs that can be rendered within the form using this method.
1017: * For example, you can now render the autocomplete tag on the QForm
1018: * additional javascript actions, etc.
1019: * $this->SetCustomAttribute("autocomplete", "off");
1020: * Will render:
1021: * [form ...... autocomplete="off"] (replace sqare brackets with angle brackets)
1022: * @param string $strName Name of the attribute
1023: * @param string $strValue Value of the attribute
1024: *
1025: * @throws QCallerException
1026: */
1027: public function SetCustomAttribute($strName, $strValue) {
1028: if ($strName == "method" || $strName == "action")
1029: throw new QCallerException(sprintf("Custom Attribute not supported through SetCustomAttribute(): %s", $strName));
1030:
1031: if (!is_null($strValue))
1032: $this->strCustomAttributeArray[$strName] = $strValue;
1033: else {
1034: $this->strCustomAttributeArray[$strName] = null;
1035: unset($this->strCustomAttributeArray[$strName]);
1036: }
1037: }
1038:
1039: /**
1040: * Returns the requested custom attribute from the form.
1041: * This attribute must have already been set.
1042: * @param string $strName Name of the Custom Attribute
1043: *
1044: * @return mixed
1045: * @throws QCallerException
1046: */
1047: public function GetCustomAttribute($strName) {
1048: if ((is_array($this->strCustomAttributeArray)) && (array_key_exists($strName, $this->strCustomAttributeArray)))
1049: return $this->strCustomAttributeArray[$strName];
1050: else
1051: throw new QCallerException(sprintf("Custom Attribute does not exist in Form: %s", $strName));
1052: }
1053:
1054: public function RemoveCustomAttribute($strName) {
1055: if ((is_array($this->strCustomAttributeArray)) && (array_key_exists($strName, $this->strCustomAttributeArray))) {
1056: $this->strCustomAttributeArray[$strName] = null;
1057: unset($this->strCustomAttributeArray[$strName]);
1058: } else
1059: throw new QCallerException(sprintf("Custom Attribute does not exist in Form: %s", $strName));
1060: }
1061:
1062:
1063: public function AddGrouping(QControlGrouping $objGrouping) {
1064: $strGroupingId = $objGrouping->GroupingId;
1065: if (array_key_exists($strGroupingId, $this->objGroupingArray))
1066: throw new QCallerException(sprintf('A Grouping already exists in the form with the ID: %s', $strGroupingId));
1067: if (array_key_exists($strGroupingId, $this->objControlArray))
1068: throw new QCallerException(sprintf('A Control already exists in the form with the ID: %s', $strGroupingId));
1069: $this->objGroupingArray[$strGroupingId] = $objGrouping;
1070: }
1071:
1072: public function GetGrouping($strGroupingId) {
1073: if (array_key_exists($strGroupingId, $this->objGroupingArray))
1074: return $this->objGroupingArray[$strGroupingId];
1075: else
1076: return null;
1077: }
1078:
1079: public function RemoveGrouping($strGroupingId) {
1080: if (array_key_exists($strGroupingId, $this->objGroupingArray)) {
1081: // Remove this Grouping
1082: unset($this->objGroupingArray[$strGroupingId]);
1083: }
1084: }
1085:
1086: /**
1087: * Retruns the Groupings
1088: * @return mixed
1089: */
1090: public function GetAllGroupings() {
1091: return $this->objGroupingArray;
1092: }
1093:
1094: /**
1095: * Returns the child controls of the current QForm or a QControl object
1096: *
1097: * @param QForm|QControl|QFormBase $objParentObject The object whose child controls are to be searched for
1098: *
1099: * @throws QCallerException
1100: * @return QControl[]
1101: */
1102: public function GetChildControls($objParentObject) {
1103: $objControlArrayToReturn = array();
1104:
1105: if ($objParentObject instanceof QForm) {
1106: // They want all the ChildControls for this Form
1107: // Basically, return all objControlArray QControls where the Qcontrol's parent is NULL
1108: foreach ($this->objControlArray as $objChildControl) {
1109: if (!($objChildControl->ParentControl))
1110: array_push($objControlArrayToReturn, $objChildControl);
1111: }
1112: return $objControlArrayToReturn;
1113:
1114: } else if ($objParentObject instanceof QControl) {
1115: return $objParentObject->GetChildControls();
1116: // THey want all the ChildControls for a specific Control
1117: // Basically, return all objControlArray QControls where the Qcontrol's parent is the passed in parentobject
1118: /* $strControlId = $objParentObject->ControlId;
1119: foreach ($this->objControlArray as $objChildControl) {
1120: $objParentControl = $objChildControl->ParentControl;
1121: if (($objParentControl) && ($objParentControl->ControlId == $strControlId)) {
1122: array_push($objControlArrayToReturn, $objChildControl);
1123: }
1124: }*/
1125:
1126: } else
1127: throw new QCallerException('ParentObject must be either a QForm or QControl object');
1128: }
1129:
1130: /**
1131: * This function evaluates the QForm Template.
1132: * It will try to open the Template file specified in the call to 'Run' method for the QForm
1133: * and then execute it.
1134: * @param string $strTemplate Path to the HTML template file
1135: *
1136: * @return string The evaluated HTML string
1137: */
1138: public function EvaluateTemplate($strTemplate) {
1139: global $_ITEM;
1140: global $_CONTROL;
1141: global $_FORM;
1142:
1143: if ($strTemplate) {
1144: QApplication::$ProcessOutput = false;
1145: // Store the Output Buffer locally
1146: $strAlreadyRendered = ob_get_contents();
1147: if ($strAlreadyRendered) {
1148: ob_clean();
1149: }
1150:
1151: // Evaluate the new template
1152: ob_start('__QForm_EvaluateTemplate_ObHandler');
1153: require($strTemplate);
1154: $strTemplateEvaluated = ob_get_contents();
1155: ob_end_clean();
1156:
1157: // Restore the output buffer and return evaluated template
1158: if ($strAlreadyRendered) {
1159: print($strAlreadyRendered);
1160: }
1161: QApplication::$ProcessOutput = true;
1162:
1163: return $strTemplateEvaluated;
1164: } else
1165: return null;
1166: }
1167:
1168: /**
1169: * Triggers an event handler method for a given control ID
1170: * NOTE: Parameters must be already validated and are guaranteed to exist.
1171: *
1172: * @param string $strControlId Control ID triggering the method
1173: * @param string $strMethodName Method name which has to be fired. Includes a control id if a control action.
1174: * @param QAction $objAction The action object which caused the event
1175: */
1176: protected function TriggerMethod($strControlId, $strMethodName, QAction $objAction) {
1177: $mixParameter = $_POST['Qform__FormParameter'];
1178: $objSourceControl = $this->objControlArray[$strControlId];
1179: $params = QControl::_ProcessActionParams($objSourceControl, $objAction, $mixParameter);
1180:
1181: if (strpos($strMethodName, '::')) {
1182: // Calling a static method in a class
1183: $f = explode('::', $strMethodName);
1184: if (is_callable($f)) {
1185: $f($this->strFormId, $params['controlId'], $params['param'], $params);
1186: }
1187: }
1188: elseif (($intPosition = strpos($strMethodName, ':')) !== false) {
1189: $strDestControlId = substr($strMethodName, 0, $intPosition);
1190: $strMethodName = substr($strMethodName, $intPosition + 1);
1191:
1192: $objDestControl = $this->objControlArray[$strDestControlId];
1193: QControl::_CallActionMethod ($objDestControl, $strMethodName, $this->strFormId, $params);
1194: } else {
1195: $this->$strMethodName($this->strFormId, $params['controlId'], $params['param'], $params);
1196: }
1197: }
1198:
1199: /**
1200: * Calles 'Validate' method on a QControl recursively
1201: * @param QControl $objControl
1202: *
1203: * @return bool
1204: */
1205: protected function ValidateControlAndChildren(QControl $objControl) {
1206: return $objControl->ValidateControlAndChildren();
1207: }
1208:
1209: /**
1210: * Runs/Triggers any and all event handling functions for the control on which an event took place
1211: * Depending on the control's CausesValidation value, it also calls for validation of the control or
1212: * control and children or entire QForm.
1213: *
1214: * @param null|string $strControlIdOverride If supplied, the control with the supplied ID is selected
1215: *
1216: * @throws Exception|QCallerException
1217: */
1218: protected function TriggerActions($strControlIdOverride = null) {
1219: if (array_key_exists('Qform__FormControl', $_POST)) {
1220: if ($strControlIdOverride) {
1221: $strControlId = $strControlIdOverride;
1222: } else {
1223: $strControlId = $_POST['Qform__FormControl'];
1224: }
1225:
1226: // Control ID determined
1227: if ($strControlId != '') {
1228: $strEvent = $_POST['Qform__FormEvent'];
1229: $strAjaxActionId = NULL;
1230:
1231: // Does this Control which performed the action exist?
1232: if (array_key_exists($strControlId, $this->objControlArray)) {
1233: // Get the ActionControl as well as the Actions to Perform
1234: $objActionControl = $this->objControlArray[$strControlId];
1235:
1236: switch ($this->strCallType) {
1237: case QCallType::Ajax:
1238: // split up event class name and ajax action id: i.e.: QClickEvent#a3 => [QClickEvent, a3]
1239: $arrTemp = explode('#',$strEvent);
1240: $strEvent = $arrTemp[0];
1241: if(count($arrTemp) == 2)
1242: $strAjaxActionId = $arrTemp[1];
1243: $objActions = $objActionControl->GetAllActions($strEvent, 'QAjaxAction');
1244: break;
1245: case QCallType::Server:
1246: $objActions = $objActionControl->GetAllActions($strEvent, 'QServerAction');
1247: break;
1248: default:
1249: throw new Exception('Unknown Form CallType: ' . $this->strCallType);
1250: }
1251:
1252: // Validation Check
1253: $blnValid = true;
1254: $mixCausesValidation = null;
1255:
1256: // Figure out what the CausesValidation directive is
1257: // Set $mixCausesValidation to the default one (e.g. the one defined on the control)
1258: $mixCausesValidation = $objActionControl->CausesValidation;
1259:
1260: // Next, go through the linked ajax/server actions to see if a causesvalidation override is set on any of them
1261: if ($objActions) foreach ($objActions as $objAction) {
1262: if (!is_null($objAction->CausesValidationOverride)) {
1263: $mixCausesValidation = $objAction->CausesValidationOverride;
1264: }
1265: }
1266:
1267: // Now, Do Something with mixCauseValidation...
1268:
1269: // Starting Point is a QControl
1270: if ($mixCausesValidation instanceof QControl) {
1271: if (!$this->ValidateControlAndChildren($mixCausesValidation)) {
1272: $blnValid = false;
1273: }
1274:
1275: // Starting Point is an Array of QControls
1276: } else if (is_array($mixCausesValidation)) {
1277: foreach (((array) $mixCausesValidation) as $objControlToValidate) {
1278: if (!$this->ValidateControlAndChildren($objControlToValidate)) {
1279: $blnValid = false;
1280: }
1281: }
1282:
1283: // Validate All the Controls on the Form
1284: } else if ($mixCausesValidation === QCausesValidation::AllControls) {
1285: foreach ($this->GetChildControls($this) as $objControl) {
1286: // Only Enabled and Visible and Rendered controls that are children of this form should be validated
1287: if (($objControl->Visible) && ($objControl->Enabled) && ($objControl->RenderMethod) && ($objControl->OnPage)) {
1288: if (!$this->ValidateControlAndChildren($objControl)) {
1289: $blnValid = false;
1290: }
1291: }
1292: }
1293:
1294: // CausesValidation specifed by QCausesValidation directive
1295: } else if ($mixCausesValidation == QCausesValidation::SiblingsAndChildren) {
1296: // Get only the Siblings of the ActionControl's ParentControl
1297: // If not ParentControl, then the parent is the form itself
1298: if (!($objParentObject = $objActionControl->ParentControl)) {
1299: $objParentObject = $this;
1300: }
1301:
1302: // Get all the children of ParentObject
1303: foreach ($this->GetChildControls($objParentObject) as $objControl) {
1304: // Only Enabled and Visible and Rendered controls that are children of ParentObject should be validated
1305: if (($objControl->Visible) && ($objControl->Enabled) && ($objControl->RenderMethod) && ($objControl->OnPage)) {
1306: if (!$this->ValidateControlAndChildren($objControl)) {
1307: $blnValid = false;
1308: }
1309: }
1310: }
1311:
1312: // CausesValidation specifed by QCausesValidation directive
1313: } else if ($mixCausesValidation == QCausesValidation::SiblingsOnly) {
1314: // Get only the Siblings of the ActionControl's ParentControl
1315: // If not ParentControl, tyhen the parent is the form itself
1316: if (!($objParentObject = $objActionControl->ParentControl)) {
1317: $objParentObject = $this;
1318: }
1319:
1320: // Get all the children of ParentObject
1321: foreach ($this->GetChildControls($objParentObject) as $objControl)
1322: // Only Enabled and Visible and Rendered controls that are children of ParentObject should be validated
1323: if (($objControl->Visible) && ($objControl->Enabled) && ($objControl->RenderMethod) && ($objControl->OnPage)) {
1324: if (!$objControl->Validate()) {
1325: $objControl->MarkAsModified();
1326: $blnValid = false;
1327: }
1328: }
1329:
1330: // No Validation Requested
1331: } else {}
1332:
1333:
1334: // Run Form-Specific Validation (if any)
1335: if ($mixCausesValidation && !($mixCausesValidation instanceof QDialog)) {
1336: if (!$this->Form_Validate()) {
1337: $blnValid = false;
1338: }
1339: }
1340:
1341:
1342: // Go ahead and run the ServerActions or AjaxActions if Validation Passed and if there are Server/Ajax-Actions defined
1343: if ($blnValid) {
1344: if ($objActions) foreach ($objActions as $objAction) {
1345: if ($strMethodName = $objAction->MethodName) {
1346: if (($strAjaxActionId == NULL) //if this call was not an ajax call
1347: || ($objAction->Id == NULL) // or the QAjaxAction derived action has no id set
1348: //(a possible way to add a callback that gets executed on every ajax call for this control)
1349: || ($strAjaxActionId == $objAction->Id)) //or the ajax action id passed from client side equals the id of the current ajax action
1350: $this->TriggerMethod($strControlId, $strMethodName, $objAction);
1351: }
1352: }
1353: }
1354: else {
1355: $this->Form_Invalid(); // notify form that something went wrong
1356: }
1357: } else {
1358: // Nope -- Throw an exception
1359: throw new Exception(sprintf('Control passed by Qform__FormControl does not exist: %s', $strControlId));
1360: }
1361: }
1362: /* else {
1363: // TODO: Code to automatically execute any PrimaryButton's onclick action, if applicable
1364: // Difficult b/c of all the QCubed hidden parameters that need to be set to get the action to work properly
1365: // Javascript interaction of PrimaryButton works fine in Firefox... currently doens't work in IE 6.
1366: }*/
1367: }
1368: }
1369:
1370: /**
1371: * Begins rendering the page
1372: */
1373: protected function Render() {
1374: if (QWatcher::WatchersChanged()) {
1375: QApplication::ExecuteJsFunction('qc.broadcastChange');
1376: }
1377:
1378: require($this->HtmlIncludeFilePath);
1379: }
1380:
1381: /**
1382: * Render the children of this QForm
1383: * @param bool $blnDisplayOutput
1384: *
1385: * @return null|string Null when blnDisplayOutput is true
1386: */
1387: protected function RenderChildren($blnDisplayOutput = true) {
1388: $strToReturn = "";
1389:
1390: foreach ($this->GetChildControls($this) as $objControl)
1391: if (!$objControl->Rendered)
1392: $strToReturn .= $objControl->Render($blnDisplayOutput);
1393:
1394: if ($blnDisplayOutput) {
1395: print($strToReturn);
1396: return null;
1397: } else
1398: return $strToReturn;
1399: }
1400:
1401: /**
1402: * This exists to prevent inadverant "New"
1403: */
1404: protected function __construct() {}
1405:
1406: /**
1407: * Renders the tags to include the css style sheets. Call this in your head tag if you want to
1408: * put these there. Otherwise, the styles will automatically be included just after the form.
1409: *
1410: * @param bool $blnDisplayOutput
1411: * @return null|string
1412: */
1413: public function RenderStyles($blnDisplayOutput = true, $blnInHead = true) {
1414: $strToReturn = '';
1415: $this->strIncludedStyleSheetFileArray = array();
1416:
1417: // Figure out initial list of StyleSheet includes
1418: $strStyleSheetArray = array();
1419:
1420: foreach ($this->GetAllControls() as $objControl) {
1421: // Include any StyleSheets? The control would have a
1422: // comma-delimited list of stylesheet files to include (if applicable)
1423: if ($strScriptArray = $this->ProcessStyleSheetList($objControl->StyleSheets)) {
1424: $strStyleSheetArray = array_merge($strStyleSheetArray, $strScriptArray);
1425: }
1426: }
1427:
1428: // In order to make ui-themes workable, move the jquery.css to the end of list.
1429: // It should override any rules that it can override.
1430: foreach ($strStyleSheetArray as $strScript) {
1431: if (__JQUERY_CSS__ == $strScript) {
1432: unset($strStyleSheetArray[$strScript]);
1433: $strStyleSheetArray[$strScript] = $strScript;
1434: break;
1435: }
1436: }
1437:
1438: // Include styles that need to be included
1439: foreach ($strStyleSheetArray as $strScript) {
1440: if ($blnInHead) {
1441: $strToReturn .= '<link href="' . $this->GetCssFileUri($strScript) . '" rel="stylesheet" />';
1442: } else {
1443: $strToReturn .= '<style type="text/css" media="all">@import "' . $this->GetCssFileUri($strScript) . '"</style>';
1444: }
1445: $strToReturn .= "\r\n";
1446: }
1447:
1448: self::$blnStylesRendered = true;
1449:
1450: // Return or Display
1451: if ($blnDisplayOutput) {
1452: if(!QApplication::$CliMode) {
1453: print($strToReturn);
1454: }
1455: return null;
1456: } else {
1457: if(!QApplication::$CliMode) {
1458: return $strToReturn;
1459: } else {
1460: return '';
1461: }
1462: }
1463: }
1464:
1465: /**
1466: * Initializes the QForm rendering process
1467: * @param bool $blnDisplayOutput Whether the output is to be printed (true) or simply returned (false)
1468: *
1469: * @return null|string
1470: * @throws QCallerException
1471: */
1472: public function RenderBegin($blnDisplayOutput = true) {
1473: // Ensure that RenderBegin() has not yet been called
1474: switch ($this->intFormStatus) {
1475: case QFormBase::FormStatusUnrendered:
1476: break;
1477: case QFormBase::FormStatusRenderBegun:
1478: case QFormBase::FormStatusRenderEnded:
1479: throw new QCallerException('$this->RenderBegin() has already been called');
1480: break;
1481: default:
1482: throw new QCallerException('FormStatus is in an unknown status');
1483: }
1484:
1485: // Update FormStatus and Clear Included JS/CSS list
1486: $this->intFormStatus = QFormBase::FormStatusRenderBegun;
1487:
1488: // Prepare for rendering
1489:
1490: QApplicationBase::$ProcessOutput = false;
1491: $strOutputtedText = trim(ob_get_contents());
1492: if (strpos(strtolower($strOutputtedText), '<body') === false) {
1493: $strToReturn = '<body>';
1494: $this->blnRenderedBodyTag = true;
1495: } else
1496: $strToReturn = '';
1497: QApplicationBase::$ProcessOutput = true;
1498:
1499:
1500: // Iterate through the form's ControlArray to Define FormAttributes and additional JavaScriptIncludes
1501: $this->strFormAttributeArray = array();
1502: foreach ($this->GetAllControls() as $objControl) {
1503: // Form Attributes?
1504: if ($objControl->FormAttributes) {
1505: $this->strFormAttributeArray = array_merge($this->strFormAttributeArray, $objControl->FormAttributes);
1506: }
1507: }
1508:
1509: if (is_array($this->strCustomAttributeArray))
1510: $this->strFormAttributeArray = array_merge($this->strFormAttributeArray, $this->strCustomAttributeArray);
1511:
1512: // Create $strFormAttributes
1513: $strFormAttributes = '';
1514: foreach ($this->strFormAttributeArray as $strKey=>$strValue) {
1515: $strFormAttributes .= sprintf(' %s="%s"', $strKey, $strValue);
1516: }
1517:
1518: if ($this->strCssClass)
1519: $strFormAttributes .= ' class="' . $this->strCssClass . '"';
1520:
1521: // Setup Rendered HTML
1522: $strToReturn .= sprintf('<form method="post" id="%s" action="%s"%s>', $this->strFormId, htmlentities(QApplication::$RequestUri), $strFormAttributes);
1523: $strToReturn .= "\r\n";
1524:
1525: if (!self::$blnStylesRendered) {
1526: $strToReturn .= $this->RenderStyles(false, false);
1527: }
1528:
1529: // Perhaps a strFormModifiers as an array to
1530: // allow controls to update other parts of the form, like enctype, onsubmit, etc.
1531:
1532: // Return or Display
1533: if ($blnDisplayOutput) {
1534: if(!QApplication::$CliMode) {
1535: print($strToReturn);
1536: }
1537: return null;
1538: } else {
1539: if(!QApplication::$CliMode) {
1540: return $strToReturn;
1541: } else {
1542: return '';
1543: }
1544: }
1545: }
1546:
1547: /**
1548: * Internal helper function used by RenderBegin and by RenderAjax
1549: * Given a comma-delimited list of javascript files, this will return an array of files that NEED to still
1550: * be included because (1) it hasn't yet been included and (2) it hasn't been specified to be "ignored".
1551: *
1552: * This WILL update the internal $strIncludedJavaScriptFileArray array.
1553: *
1554: * @param string | array $strJavaScriptFileList
1555: * @return string[] array of script files to include or NULL if none
1556: */
1557: protected function ProcessJavaScriptList($strJavaScriptFileList) {
1558:
1559: if (empty($strJavaScriptFileList)) return null;
1560:
1561: $strArrayToReturn = array();
1562:
1563: if (!is_array($strJavaScriptFileList)) {
1564: $strJavaScriptFileList = explode(',', $strJavaScriptFileList);
1565: }
1566:
1567: // Iterate through the list of JavaScriptFiles to Include...
1568: foreach ($strJavaScriptFileList as $strScript) {
1569: if ($strScript = trim($strScript)) {
1570:
1571: // Include it if we're NOT ignoring it and it has NOT already been included
1572: if ((array_search($strScript, $this->strIgnoreJavaScriptFileArray) === false) &&
1573: !array_key_exists($strScript, $this->strIncludedJavaScriptFileArray)) {
1574: $strArrayToReturn[$strScript] = $strScript;
1575: $this->strIncludedJavaScriptFileArray[$strScript] = true;
1576: }
1577: }
1578: }
1579:
1580: if (count($strArrayToReturn))
1581: return $strArrayToReturn;
1582:
1583: return null;
1584: }
1585:
1586: /**
1587: * Primarily used by RenderBegin and by RenderAjax
1588: * Given a comma-delimited list of stylesheet files, this will return an array of file that NEED to still
1589: * be included because (1) it hasn't yet been included and (2) it hasn't been specified to be "ignored".
1590: *
1591: * This WILL update the internal $strIncludedStyleSheetFileArray array.
1592: *
1593: * @param string $strStyleSheetFileList
1594: * @return string[] array of stylesheet files to include or NULL if none
1595: */
1596: protected function ProcessStyleSheetList($strStyleSheetFileList) {
1597: $strArrayToReturn = array();
1598:
1599: // Is there a comma-delimited list of StyleSheet files to include?
1600: if ($strStyleSheetFileList = trim($strStyleSheetFileList)) {
1601: $strScriptArray = explode(',', $strStyleSheetFileList);
1602:
1603: // Iterate through the list of StyleSheetFiles to Include...
1604: foreach ($strScriptArray as $strScript)
1605: if ($strScript = trim($strScript))
1606:
1607: // Include it if we're NOT ignoring it and it has NOT already been included
1608: if ((array_search($strScript, $this->strIgnoreStyleSheetFileArray) === false) &&
1609: !array_key_exists($strScript, $this->strIncludedStyleSheetFileArray)) {
1610: $strArrayToReturn[$strScript] = $strScript;
1611: $this->strIncludedStyleSheetFileArray[$strScript] = true;
1612: }
1613: }
1614:
1615: if (count($strArrayToReturn))
1616: return $strArrayToReturn;
1617:
1618: return null;
1619: }
1620:
1621: /**
1622: * Returns whether or not this Form is being run due to a PostBack event (e.g. a ServerAction or AjaxAction)
1623: * @return bool
1624: */
1625: public function IsPostBack() {
1626: return ($this->strCallType != QCallType::None);
1627: }
1628:
1629: /**
1630: * Will return an array of Strings which will show all the error and warning messages
1631: * in all the controls in the form.
1632: *
1633: * @param bool $blnErrorsOnly Show only the errors (otherwise, show both warnings and errors)
1634: * @return string[] an array of strings representing the (multiple) errors and warnings
1635: */
1636: public function GetErrorMessages($blnErrorsOnly = false) {
1637: $strToReturn = array();
1638: foreach ($this->GetAllControls() as $objControl) {
1639: if ($objControl->ValidationError)
1640: array_push($strToReturn, $objControl->ValidationError);
1641: if (!$blnErrorsOnly)
1642: if ($objControl->Warning)
1643: array_push($strToReturn, $objControl->Warning);
1644: }
1645:
1646: return $strToReturn;
1647: }
1648:
1649: /**
1650: * Will return an array of QControls from the form which have either an error or warning message.
1651: *
1652: * @param bool $blnErrorsOnly Return controls that have just errors (otherwise, show both warnings and errors)
1653: * @return QControl[] an array of controls representing the (multiple) errors and warnings
1654: */
1655: public function GetErrorControls($blnErrorsOnly = false) {
1656: $objToReturn = array();
1657: foreach ($this->GetAllControls() as $objControl) {
1658: if ($objControl->ValidationError)
1659: array_push($objToReturn, $objControl);
1660: else if (!$blnErrorsOnly)
1661: if ($objControl->Warning)
1662: array_push($objToReturn, $objControl);
1663: }
1664:
1665: return $objToReturn;
1666: }
1667:
1668: /**
1669: * Gets the JS file URI, given a string input
1670: * @param string $strFile File name to be tested
1671: *
1672: * @return string the final JS file URI
1673: */
1674: public function GetJsFileUri($strFile) {
1675: return QApplication::GetJsFileUri($strFile);
1676: }
1677:
1678: /**
1679: * Gets the CSS file URI, given a string input
1680: * @param string $strFile File name to be tested
1681: *
1682: * @return string the final CSS URI
1683: */
1684: public function GetCssFileUri($strFile) {
1685: return QApplication::GetCssFileUri($strFile);
1686: }
1687:
1688: /**
1689: * Get high level form javascript files to be included. Default here includes all
1690: * javascripts needed to run qcubed.
1691: * Override and add to this list and include
1692: * javascript and jQuery files and libraries needed for your application.
1693: * Javascript files included before __QCUBED_JS_CORE__ can refer to jQuery as $.
1694: * After qcubed.js, $ becomes $j, so add other libraries that need
1695: * $ in a different context after qcubed.js, and insert jQuery libraries and plugins that
1696: * refer to $ before qcubed.js file.
1697: *
1698: * @return array
1699: */
1700: protected function GetFormJavaScripts() {
1701: return array (__JQUERY_BASE__,
1702: __JQUERY_EFFECTS__,
1703: 'jquery/jquery.ajaxq-0.0.1.js',
1704: __QCUBED_JS_CORE__);
1705: }
1706:
1707: /**
1708: * Renders the end of the form, including the closing form and body tags.
1709: * Renders the html for hidden controls.
1710: * @param bool $blnDisplayOutput should the output be returned or directly printed to screen.
1711: *
1712: * @return null|string
1713: * @throws QCallerException
1714: */
1715: public function RenderEnd($blnDisplayOutput = true) {
1716: // Ensure that RenderEnd() has not yet been called
1717: switch ($this->intFormStatus) {
1718: case QFormBase::FormStatusUnrendered:
1719: throw new QCallerException('$this->RenderBegin() was never called');
1720: case QFormBase::FormStatusRenderBegun:
1721: break;
1722: case QFormBase::FormStatusRenderEnded:
1723: throw new QCallerException('$this->RenderEnd() has already been called');
1724: break;
1725: default:
1726: throw new QCallerException('FormStatus is in an unknown status');
1727: }
1728:
1729: $strHtml = ''; // This will be the final output
1730:
1731: /**** Render any controls that get automatically rendered ****/
1732: foreach ($this->GetAllControls() as $objControl) {
1733: if ($objControl->AutoRender &&
1734: !$objControl->Rendered) {
1735: $strRenderMethod = $objControl->PreferredRenderMethod;
1736: $strHtml .= $objControl->$strRenderMethod(false) . _nl();
1737: }
1738: }
1739:
1740: /**** Prepare Javascripts ****/
1741:
1742: // Clear included javascript array since we are completely redrawing the page
1743: $this->strIncludedJavaScriptFileArray = array();
1744: $strControlIdToRegister = array();
1745: $strEventScripts = '';
1746:
1747: // Add form level javascripts and libraries
1748: $strJavaScriptArray = $this->ProcessJavaScriptList($this->GetFormJavaScripts());
1749: QApplication::AddJavaScriptFiles($strJavaScriptArray);
1750: $strFormJsFiles = QApplication::RenderFiles(); // Render the form-level javascript files separately
1751:
1752: // Go through all controls and gather up any JS or CSS to run or Form Attributes to modify
1753: foreach ($this->GetAllControls() as $objControl) {
1754: if ($objControl->Rendered || $objControl->ScriptsOnly) {
1755: $strControlIdToRegister[] = $objControl->ControlId;
1756:
1757: /* Note: GetEndScript may cause the control to register additional commands, or even add javascripts, so those should be handled after this. */
1758: if ($strControlScript = $objControl->GetEndScript()) {
1759: $strControlScript = JavaScriptHelper::TerminateScript($strControlScript);
1760:
1761: // Add comments for developer version of output
1762: if (!QApplication::$Minimize) {
1763: // Render a comment
1764: $strControlScript = _nl() . _nl() .
1765: sprintf ('/*** EndScript -- Control Type: %s, Control Name: %s, Control Id: %s ***/',
1766: get_class($objControl), $objControl->Name, $objControl->ControlId) .
1767: _nl() .
1768: _indent($strControlScript);
1769: }
1770: $strEventScripts .= $strControlScript;
1771: }
1772: }
1773:
1774: // Include the javascripts specified by each control.
1775: if ($strScriptArray = $this->ProcessJavaScriptList($objControl->JavaScripts)) {
1776: QApplication::AddJavaScriptFiles($strScriptArray);
1777: }
1778:
1779: // Include any StyleSheets? The control would have a
1780: // comma-delimited list of stylesheet files to include (if applicable)
1781: if ($strScriptArray = $this->ProcessStyleSheetList($objControl->StyleSheets)) {
1782: QApplication::AddStyleSheets(array_keys($strScriptArray));
1783: }
1784:
1785: // Form Attributes?
1786: if ($objControl->FormAttributes) {
1787: QApplication::ExecuteControlCommand($this->strFormId, 'attr', $objControl->FormAttributes);
1788: foreach ($objControl->FormAttributes as $strKey=>$strValue) {
1789: if (!array_key_exists($strKey, $this->strFormAttributeArray)) {
1790: $this->strFormAttributeArray[$strKey] = $strValue;
1791: } else if ($this->strFormAttributeArray[$strKey] != $strValue) {
1792: $this->strFormAttributeArray[$strKey] = $strValue;
1793: }
1794: }
1795: }
1796: }
1797:
1798: // Add grouping commands to events (Used for deprecated drag and drop, but not removed yet)
1799: foreach ($this->objGroupingArray as $objGrouping) {
1800: $strGroupingScript = $objGrouping->Render();
1801: if (strlen($strGroupingScript) > 0) {
1802: $strGroupingScript = JavaScriptHelper::TerminateScript($strGroupingScript);
1803: $strEventScripts .= $strGroupingScript;
1804: }
1805: }
1806:
1807: /*** Build the javascript block ****/
1808:
1809: // Start with variable settings and initForm
1810: $strEndScript = sprintf('qc.initForm("%s"); ', $this->strFormId);
1811:
1812: // Register controls
1813: if ($strControlIdToRegister) {
1814: $strEndScript .= sprintf("qc.regCA(%s); \n", JavaScriptHelper::toJsObject($strControlIdToRegister));
1815: }
1816:
1817: // Design mode event
1818: if (defined('__DESIGN_MODE__') && __DESIGN_MODE__ == 1) {
1819: // attach an event listener to the form to send context menu selections to the designer dialog for processing
1820: $strEndScript .= sprintf(
1821: '$j("#%s").on("contextmenu", "[id]",
1822: function(event) {
1823: $j("#qconnectoreditdlg").trigger("qdesignerclick",
1824: [{id: event.target.id ? event.target.id : $j(event.target).parents("[id]").attr("id"), for: $j(event.target).attr("for")}]
1825: );
1826: return false;
1827: }
1828: );', $this->FormId);
1829: }
1830:
1831: // Add any application level js commands.
1832: // This will include high and medimum level commands
1833: $strEndScript .= QApplication::RenderJavascript(true);
1834:
1835: // Add the javascript coming from controls and events just after the medium level commands
1836: $strEndScript .= ';' . $strEventScripts;
1837:
1838: // Add low level commands and other things that need to execute at the end
1839: $strEndScript .= ';' . QApplication::RenderJavascript(false);
1840:
1841:
1842:
1843:
1844: // Create Final EndScript Script
1845: $strEndScript = sprintf('<script type="text/javascript">$j(document).ready(function() { %s; });</script>', $strEndScript);
1846:
1847:
1848: /**** Render the HTML itself, appending the javascript we generated above ****/
1849:
1850: foreach ($this->GetAllControls() as $objControl) {
1851: if ($objControl->Rendered) {
1852: $strHtml .= $objControl->GetEndHtml();
1853: }
1854: $objControl->ResetFlags(); // Make sure controls are serialized in a reset state
1855: }
1856:
1857: $strHtml .= $strFormJsFiles . _nl(); // Add form level javascript files
1858:
1859: // put javascript environment defines up early for use by other js files.
1860: $strHtml .= '<script type="text/javascript">' .
1861: sprintf('qc.baseDir = "%s"; ', __VIRTUAL_DIRECTORY__ . __SUBDIRECTORY__) .
1862: sprintf('qc.jsAssets = "%s"; ', __VIRTUAL_DIRECTORY__ . __JS_ASSETS__) .
1863: sprintf('qc.phpAssets = "%s"; ', __VIRTUAL_DIRECTORY__ . __PHP_ASSETS__) .
1864: sprintf('qc.cssAssets = "%s"; ', __VIRTUAL_DIRECTORY__ . __CSS_ASSETS__) .
1865: sprintf('qc.imageAssets = "%s"; ', __VIRTUAL_DIRECTORY__ . __IMAGE_ASSETS__) .
1866: '</script>' .
1867: _nl();
1868:
1869: $strHtml .= QApplication::RenderFiles() . _nl(); // add plugin and control js files
1870:
1871: // Render hidden controls related to the form
1872: $strHtml .= sprintf('<input type="hidden" name="Qform__FormId" id="Qform__FormId" value="%s" />', $this->strFormId) . _nl();
1873: $strHtml .= sprintf('<input type="hidden" name="Qform__FormControl" id="Qform__FormControl" value="" />') . _nl();
1874: $strHtml .= sprintf('<input type="hidden" name="Qform__FormEvent" id="Qform__FormEvent" value="" />') . _nl();
1875: $strHtml .= sprintf('<input type="hidden" name="Qform__FormParameter" id="Qform__FormParameter" value="" />') . _nl();
1876: $strHtml .= sprintf('<input type="hidden" name="Qform__FormCallType" id="Qform__FormCallType" value="" />') . _nl();
1877: $strHtml .= sprintf('<input type="hidden" name="Qform__FormUpdates" id="Qform__FormUpdates" value="" />') . _nl();
1878: $strHtml .= sprintf('<input type="hidden" name="Qform__FormCheckableControls" id="Qform__FormCheckableControls" value="" />') . _nl();
1879:
1880: // Serialize and write out the formstate
1881: $strHtml .= sprintf('<input type="hidden" name="Qform__FormState" id="Qform__FormState" value="%s" />', QForm::Serialize(clone($this))) . _nl();
1882:
1883: // close the form tag
1884: $strHtml .= "</form>";
1885:
1886: // Add the JavaScripts rendered above
1887: $strHtml .= $strEndScript;
1888:
1889: // close the body tag
1890: if ($this->blnRenderedBodyTag) {
1891: $strHtml .= '</body>';
1892: }
1893:
1894: /**** Cleanup ****/
1895:
1896: // Update Form Status
1897: $this->intFormStatus = QFormBase::FormStatusRenderEnded;
1898:
1899: // Display or Return
1900: if ($blnDisplayOutput) {
1901: if(!QApplication::$CliMode) {
1902: print($strHtml);
1903: }
1904: return null;
1905: } else {
1906: if(!QApplication::$CliMode) {
1907: return $strHtml;
1908: } else {
1909: return '';
1910: }
1911: }
1912: }
1913: }
1914:
1915: function __QForm_EvaluateTemplate_ObHandler($strBuffer) {
1916: return $strBuffer;
1917: }
1918: