Merge pull request #3211 from eileenmcnaughton/comments
[civicrm-core.git] / CRM / Core / Form.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * This is our base form. It is part of the Form/Controller/StateMachine
30 * trifecta. Each form is associated with a specific state in the state
31 * machine. Each form can also operate in various modes
32 *
33 * @package CRM
34 * @copyright CiviCRM LLC (c) 2004-2014
35 * $Id$
36 *
37 */
38
39 require_once 'HTML/QuickForm/Page.php';
40 class CRM_Core_Form extends HTML_QuickForm_Page {
41
42 /**
43 * The state object that this form belongs to
44 * @var object
45 */
46 protected $_state;
47
48 /**
49 * The name of this form
50 * @var string
51 */
52 protected $_name;
53
54 /**
55 * The title of this form
56 * @var string
57 */
58 protected $_title = NULL;
59
60 /**
61 * The options passed into this form
62 * @var mixed
63 */
64 protected $_options = NULL;
65
66 /**
67 * The mode of operation for this form
68 * @var int
69 */
70 protected $_action;
71
72 /**
73 * the renderer used for this form
74 *
75 * @var object
76 */
77 protected $_renderer;
78
79 /**
80 * An array to hold a list of datefields on the form
81 * so that they can be converted to ISO in a consistent manner
82 *
83 * @var array
84 *
85 * e.g on a form declare $_dateFields = array(
86 * 'receive_date' => array('default' => 'now'),
87 * );
88 * then in postProcess call $this->convertDateFieldsToMySQL($formValues)
89 * to have the time field re-incorporated into the field & 'now' set if
90 * no value has been passed in
91 */
92 protected $_dateFields = array();
93
94 /**
95 * cache the smarty template for efficiency reasons
96 *
97 * @var CRM_Core_Smarty
98 */
99 static protected $_template;
100
101 /**
102 * Indicate if this form should warn users of unsaved changes
103 */
104 protected $unsavedChangesWarn;
105
106 /**
107 * What to return to the client if in ajax mode (snippet=json)
108 *
109 * @var array
110 */
111 public $ajaxResponse = array();
112
113 /**
114 * Url path used to reach this page
115 *
116 * @var array
117 */
118 public $urlPath = array();
119
120 /**
121 * constants for attributes for various form elements
122 * attempt to standardize on the number of variations that we
123 * use of the below form elements
124 *
125 * @var const string
126 */
127 CONST ATTR_SPACING = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
128
129 /**
130 * All checkboxes are defined with a common prefix. This allows us to
131 * have the same javascript to check / clear all the checkboxes etc
132 * If u have multiple groups of checkboxes, you will need to give them different
133 * ids to avoid potential name collision
134 *
135 * @var const string / int
136 */
137 CONST CB_PREFIX = 'mark_x_', CB_PREFIY = 'mark_y_', CB_PREFIZ = 'mark_z_', CB_PREFIX_LEN = 7;
138
139 /**
140 * Constructor for the basic form page
141 *
142 * We should not use QuickForm directly. This class provides a lot
143 * of default convenient functions, rules and buttons
144 *
145 * @param object $state State associated with this form
146 * @param \const|\enum $action The mode the form is operating in (None/Create/View/Update/Delete)
147 * @param string $method The type of http method used (GET/POST)
148 * @param string $name The name of the form if different from class name
149 *
150 * @return \CRM_Core_Form
151 @access public
152 */
153 function __construct(
154 $state = NULL,
155 $action = CRM_Core_Action::NONE,
156 $method = 'post',
157 $name = NULL
158 ) {
159
160 if ($name) {
161 $this->_name = $name;
162 }
163 else {
164 $this->_name = CRM_Utils_String::getClassName(CRM_Utils_System::getClassName($this));
165 }
166
167 $this->HTML_QuickForm_Page($this->_name, $method);
168
169 $this->_state =& $state;
170 if ($this->_state) {
171 $this->_state->setName($this->_name);
172 }
173 $this->_action = (int) $action;
174
175 $this->registerRules();
176
177 // let the constructor initialize this, should happen only once
178 if (!isset(self::$_template)) {
179 self::$_template = CRM_Core_Smarty::singleton();
180 }
181
182 $this->assign('snippet', CRM_Utils_Array::value('snippet', $_GET));
183 }
184
185 static function generateID() {
186 }
187
188 /**
189 * register all the standard rules that most forms potentially use
190 *
191 * @return void
192 * @access private
193 *
194 */
195 function registerRules() {
196 static $rules = array(
197 'title', 'longTitle', 'variable', 'qfVariable',
198 'phone', 'integer', 'query',
199 'url', 'wikiURL',
200 'domain', 'numberOfDigit',
201 'date', 'currentDate',
202 'asciiFile', 'htmlFile', 'utf8File',
203 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
204 'xssString', 'fileExists', 'autocomplete', 'validContact',
205 );
206
207 foreach ($rules as $rule) {
208 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
209 }
210 }
211
212 /**
213 * Simple easy to use wrapper around addElement. Deal with
214 * simple validation rules
215 *
216 * @param $type
217 * @param $name
218 * @param string $label
219 * @param string $attributes
220 * @param bool $required
221 * @param null $extra
222 *
223 * @internal param \type $string of html element to be added
224 * @internal param \name $string of the html element
225 * @internal param \display $string label for the html element
226 * @internal param \attributes $string used for this element.
227 * These are not default values
228 * @internal param \is $bool this a required field
229 *
230 * @return HTML_QuickForm_Element could be an error object
231 * @access public
232 */
233 function &add($type, $name, $label = '',
234 $attributes = '', $required = FALSE, $extra = NULL
235 ) {
236 // Normalize this property
237 if ($type == 'select' && is_array($extra) && !empty($extra['multiple'])) {
238 $extra['multiple'] = 'multiple';
239 }
240 $element = $this->addElement($type, $name, $label, $attributes, $extra);
241 if (HTML_QuickForm::isError($element)) {
242 CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
243 }
244
245 if ($required) {
246 if ($type == 'file') {
247 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
248 }
249 else {
250 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
251 }
252 if (HTML_QuickForm::isError($error)) {
253 CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
254 }
255 }
256
257 return $element;
258 }
259
260 /**
261 * This function is called before buildForm. Any pre-processing that
262 * needs to be done for buildForm should be done here
263 *
264 * This is a virtual function and should be redefined if needed
265 *
266 * @access public
267 *
268 * @return void
269 *
270 */
271 function preProcess() {}
272
273 /**
274 * This function is called after the form is validated. Any
275 * processing of form state etc should be done in this function.
276 * Typically all processing associated with a form should be done
277 * here and relevant state should be stored in the session
278 *
279 * This is a virtual function and should be redefined if needed
280 *
281 * @access public
282 *
283 * @return void
284 *
285 */
286 function postProcess() {}
287
288 /**
289 * This function is just a wrapper, so that we can call all the hook functions
290 */
291 function mainProcess() {
292 $this->postProcess();
293 $this->postProcessHook();
294
295 // Respond with JSON if in AJAX context (also support legacy value '6')
296 if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty::PRINT_JSON, 6))) {
297 $this->ajaxResponse['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller->getButtonName());
298 $this->ajaxResponse['action'] = $this->_action;
299 if (isset($this->_id) || isset($this->id)) {
300 $this->ajaxResponse['id'] = isset($this->id) ? $this->id : $this->_id;
301 }
302 CRM_Core_Page_AJAX::returnJsonResponse($this->ajaxResponse);
303 }
304 }
305
306 /**
307 * The postProcess hook is typically called by the framework
308 * However in a few cases, the form exits or redirects early in which
309 * case it needs to call this function so other modules can do the needful
310 * Calling this function directly should be avoided if possible. In general a
311 * better way is to do setUserContext so the framework does the redirect
312 *
313 */
314 function postProcessHook() {
315 CRM_Utils_Hook::postProcess(get_class($this), $this);
316 }
317
318 /**
319 * This virtual function is used to build the form. It replaces the
320 * buildForm associated with QuickForm_Page. This allows us to put
321 * preProcess in front of the actual form building routine
322 *
323 * @access public
324 *
325 * @return void
326 *
327 */
328 function buildQuickForm() {}
329
330 /**
331 * This virtual function is used to set the default values of
332 * various form elements
333 *
334 * access public
335 *
336 * @return array reference to the array of default values
337 *
338 */
339 function setDefaultValues() {}
340
341 /**
342 * This is a virtual function that adds group and global rules to
343 * the form. Keeping it distinct from the form to keep code small
344 * and localized in the form building code
345 *
346 * @access public
347 *
348 * @return void
349 *
350 */
351 function addRules() {}
352
353 function validate() {
354 $error = parent::validate();
355
356 $hookErrors = CRM_Utils_Hook::validate(
357 get_class($this),
358 $this->_submitValues,
359 $this->_submitFiles,
360 $this
361 );
362
363 if (!is_array($hookErrors)) {
364 $hookErrors = array();
365 }
366
367 CRM_Utils_Hook::validateForm(
368 get_class($this),
369 $this->_submitValues,
370 $this->_submitFiles,
371 $this,
372 $hookErrors
373 );
374
375 if (!empty($hookErrors)) {
376 $this->_errors += $hookErrors;
377 }
378
379 return (0 == count($this->_errors));
380 }
381
382 /**
383 * Core function that builds the form. We redefine this function
384 * here and expect all CRM forms to build their form in the function
385 * buildQuickForm.
386 *
387 */
388 function buildForm() {
389 $this->_formBuilt = TRUE;
390
391 $this->preProcess();
392
393 $this->assign('translatePermission', CRM_Core_Permission::check('translate CiviCRM'));
394
395 if (
396 $this->controller->_key &&
397 $this->controller->_generateQFKey
398 ) {
399 $this->addElement('hidden', 'qfKey', $this->controller->_key);
400 $this->assign('qfKey', $this->controller->_key);
401
402 }
403
404 // _generateQFKey suppresses the qfKey generation on form snippets that
405 // are part of other forms, hence we use that to avoid adding entryURL
406 if ($this->controller->_generateQFKey && $this->controller->_entryURL) {
407 $this->addElement('hidden', 'entryURL', $this->controller->_entryURL);
408 }
409
410 $this->buildQuickForm();
411
412 $defaults = $this->setDefaultValues();
413 unset($defaults['qfKey']);
414
415 if (!empty($defaults)) {
416 $this->setDefaults($defaults);
417 }
418
419 // call the form hook
420 // also call the hook function so any modules can set thier own custom defaults
421 // the user can do both the form and set default values with this hook
422 CRM_Utils_Hook::buildForm(get_class($this), $this);
423
424 $this->addRules();
425
426 //Set html data-attribute to enable warning user of unsaved changes
427 if ($this->unsavedChangesWarn === true
428 || (!isset($this->unsavedChangesWarn)
429 && ($this->_action & CRM_Core_Action::ADD || $this->_action & CRM_Core_Action::UPDATE)
430 )
431 ) {
432 $this->setAttribute('data-warn-changes', 'true');
433 }
434 }
435
436 /**
437 * Add default Next / Back buttons
438 *
439 * @param array array of associative arrays in the order in which the buttons should be
440 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
441 * The base form class will define a bunch of static arrays for commonly used
442 * formats
443 *
444 * @return void
445 *
446 * @access public
447 *
448 */
449 function addButtons($params) {
450 $prevnext = array();
451 $spacing = array();
452 foreach ($params as $button) {
453 $js = CRM_Utils_Array::value('js', $button);
454 $isDefault = CRM_Utils_Array::value('isDefault', $button, FALSE);
455 if ($isDefault) {
456 $attrs = array('class' => 'form-submit default');
457 }
458 else {
459 $attrs = array('class' => 'form-submit');
460 }
461
462 if ($js) {
463 $attrs = array_merge($js, $attrs);
464 }
465
466 if ($button['type'] === 'cancel') {
467 $attrs['class'] .= ' cancel';
468 }
469
470 if ($button['type'] === 'reset') {
471 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
472 }
473 else {
474 if (!empty($button['subName'])) {
475 $buttonName = $this->getButtonName($button['type'], $button['subName']);
476 }
477 else {
478 $buttonName = $this->getButtonName($button['type']);
479 }
480
481 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
482 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
483 }
484 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
485 }
486 if (!empty($button['isDefault'])) {
487 $this->setDefaultAction($button['type']);
488 }
489
490 // if button type is upload, set the enctype
491 if ($button['type'] == 'upload') {
492 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
493 $this->setMaxFileSize();
494 }
495
496 // hack - addGroup uses an array to express variable spacing, read from the last element
497 $spacing[] = CRM_Utils_Array::value('spacing', $button, self::ATTR_SPACING);
498 }
499 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
500 }
501
502 /**
503 * getter function for Name
504 *
505 * @return string
506 * @access public
507 */
508 function getName() {
509 return $this->_name;
510 }
511
512 /**
513 * getter function for State
514 *
515 * @return object
516 * @access public
517 */
518 function &getState() {
519 return $this->_state;
520 }
521
522 /**
523 * getter function for StateType
524 *
525 * @return int
526 * @access public
527 */
528 function getStateType() {
529 return $this->_state->getType();
530 }
531
532 /**
533 * getter function for title. Should be over-ridden by derived class
534 *
535 * @return string
536 * @access public
537 */
538 function getTitle() {
539 return $this->_title ? $this->_title : ts('ERROR: Title is not Set');
540 }
541
542 /**
543 * setter function for title.
544 *
545 * @param string $title the title of the form
546 *
547 * @return void
548 * @access public
549 */
550 function setTitle($title) {
551 $this->_title = $title;
552 }
553
554 /**
555 * Setter function for options
556 *
557 * @param mixed
558 *
559 * @return void
560 * @access public
561 */
562 function setOptions($options) {
563 $this->_options = $options;
564 }
565
566 /**
567 * getter function for link.
568 *
569 * @return string
570 * @access public
571 */
572 function getLink() {
573 $config = CRM_Core_Config::singleton();
574 return CRM_Utils_System::url($_GET[$config->userFrameworkURLVar],
575 '_qf_' . $this->_name . '_display=true'
576 );
577 }
578
579 /**
580 * boolean function to determine if this is a one form page
581 *
582 * @return boolean
583 * @access public
584 */
585 function isSimpleForm() {
586 return $this->_state->getType() & (CRM_Core_State::START | CRM_Core_State::FINISH);
587 }
588
589 /**
590 * getter function for Form Action
591 *
592 * @return string
593 * @access public
594 */
595 function getFormAction() {
596 return $this->_attributes['action'];
597 }
598
599 /**
600 * setter function for Form Action
601 *
602 * @param string
603 *
604 * @return void
605 * @access public
606 */
607 function setFormAction($action) {
608 $this->_attributes['action'] = $action;
609 }
610
611 /**
612 * render form and return contents
613 *
614 * @return string
615 * @access public
616 */
617 function toSmarty() {
618 $renderer = $this->getRenderer();
619 $this->accept($renderer);
620 $content = $renderer->toArray();
621 $content['formName'] = $this->getName();
622 return $content;
623 }
624
625 /**
626 * getter function for renderer. If renderer is not set
627 * create one and initialize it
628 *
629 * @return object
630 * @access public
631 */
632 function &getRenderer() {
633 if (!isset($this->_renderer)) {
634 $this->_renderer = CRM_Core_Form_Renderer::singleton();
635 }
636 return $this->_renderer;
637 }
638
639 /**
640 * Use the form name to create the tpl file name
641 *
642 * @return string
643 * @access public
644 */
645 function getTemplateFileName() {
646 $ext = CRM_Extension_System::singleton()->getMapper();
647 if ($ext->isExtensionClass(CRM_Utils_System::getClassName($this))) {
648 $filename = $ext->getTemplateName(CRM_Utils_System::getClassName($this));
649 $tplname = $ext->getTemplatePath(CRM_Utils_System::getClassName($this)) . DIRECTORY_SEPARATOR . $filename;
650 }
651 else {
652 $tplname = str_replace('_',
653 DIRECTORY_SEPARATOR,
654 CRM_Utils_System::getClassName($this)
655 ) . '.tpl';
656 }
657 return $tplname;
658 }
659
660 /**
661 * A wrapper for getTemplateFileName that includes calling the hook to
662 * prevent us from having to copy & paste the logic of calling the hook
663 */
664 function getHookedTemplateFileName() {
665 $pageTemplateFile = $this->getTemplateFileName();
666 CRM_Utils_Hook::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
667 return $pageTemplateFile;
668 }
669
670 /**
671 * Default extra tpl file basically just replaces .tpl with .extra.tpl
672 * i.e. we dont override
673 *
674 * @return string
675 * @access public
676 */
677 function overrideExtraTemplateFileName() {
678 return NULL;
679 }
680
681 /**
682 * Error reporting mechanism
683 *
684 * @param string $message Error Message
685 * @param int $code Error Code
686 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
687 *
688 * @return void
689 * @access public
690 */
691 function error($message, $code = NULL, $dao = NULL) {
692 if ($dao) {
693 $dao->query('ROLLBACK');
694 }
695
696 $error = CRM_Core_Error::singleton();
697
698 $error->push($code, $message);
699 }
700
701 /**
702 * Store the variable with the value in the form scope
703 *
704 * @param string name : name of the variable
705 * @param mixed value : value of the variable
706 *
707 * @access public
708 *
709 * @return void
710 *
711 */
712 function set($name, $value) {
713 $this->controller->set($name, $value);
714 }
715
716 /**
717 * Get the variable from the form scope
718 *
719 * @param string name : name of the variable
720 *
721 * @access public
722 *
723 * @return mixed
724 *
725 */
726 function get($name) {
727 return $this->controller->get($name);
728 }
729
730 /**
731 * getter for action
732 *
733 * @return int
734 * @access public
735 */
736 function getAction() {
737 return $this->_action;
738 }
739
740 /**
741 * setter for action
742 *
743 * @param int $action the mode we want to set the form
744 *
745 * @return void
746 * @access public
747 */
748 function setAction($action) {
749 $this->_action = $action;
750 }
751
752 /**
753 * assign value to name in template
754 *
755 * @param array|string $name name of variable
756 * @param mixed $value value of varaible
757 *
758 * @return void
759 * @access public
760 */
761 function assign($var, $value = NULL) {
762 self::$_template->assign($var, $value);
763 }
764
765 /**
766 * assign value to name in template by reference
767 *
768 * @param array|string $name name of variable
769 * @param mixed $value value of varaible
770 *
771 * @return void
772 * @access public
773 */
774 function assign_by_ref($var, &$value) {
775 self::$_template->assign_by_ref($var, $value);
776 }
777
778 /**
779 * appends values to template variables
780 *
781 * @param array|string $tpl_var the template variable name(s)
782 * @param mixed $value the value to append
783 * @param bool $merge
784 */
785 function append($tpl_var, $value=NULL, $merge=FALSE) {
786 self::$_template->append($tpl_var, $value, $merge);
787 }
788
789 /**
790 * Returns an array containing template variables
791 *
792 * @param string $name
793 * @param string $type
794 * @return array
795 */
796 function get_template_vars($name=null) {
797 return self::$_template->get_template_vars($name);
798 }
799
800 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
801 $options = array();
802 $attributes = $attributes ? $attributes : array();
803 $allowClear = !empty($attributes['allowClear']);
804 unset($attributes['allowClear']);
805 $attributes += array('id_suffix' => $name);
806 foreach ($values as $key => $var) {
807 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
808 }
809 $group = $this->addGroup($options, $name, $title, $separator);
810 if ($required) {
811 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
812 }
813 if ($allowClear) {
814 $group->setAttribute('allowClear', TRUE);
815 }
816 return $group;
817 }
818
819 function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
820 $attributes += array('id_suffix' => $id);
821 $choice = array();
822 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
823 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
824
825 $group = $this->addGroup($choice, $id, $title);
826 if ($allowClear) {
827 $group->setAttribute('allowClear', TRUE);
828 }
829 if ($required) {
830 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
831 }
832 }
833
834 function addCheckBox($id, $title, $values, $other = NULL,
835 $attributes = NULL, $required = NULL,
836 $javascriptMethod = NULL,
837 $separator = '<br />', $flipValues = FALSE
838 ) {
839 $options = array();
840
841 if ($javascriptMethod) {
842 foreach ($values as $key => $var) {
843 if (!$flipValues) {
844 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
845 }
846 else {
847 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
848 }
849 }
850 }
851 else {
852 foreach ($values as $key => $var) {
853 if (!$flipValues) {
854 $options[] = $this->createElement('checkbox', $var, NULL, $key);
855 }
856 else {
857 $options[] = $this->createElement('checkbox', $key, NULL, $var);
858 }
859 }
860 }
861
862 $this->addGroup($options, $id, $title, $separator);
863
864 if ($other) {
865 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
866 }
867
868 if ($required) {
869 $this->addRule($id,
870 ts('%1 is a required field.', array(1 => $title)),
871 'required'
872 );
873 }
874 }
875
876 function resetValues() {
877 $data = $this->controller->container();
878 $data['values'][$this->_name] = array();
879 }
880
881 /**
882 * simple shell that derived classes can call to add buttons to
883 * the form with a customized title for the main Submit
884 *
885 * @param string $title title of the main button
886 * @param string $type button type for the form after processing
887 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
888 *
889 * @return void
890 * @access public
891 */
892 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
893 $buttons = array();
894 if ($backType != NULL) {
895 $buttons[] = array(
896 'type' => $backType,
897 'name' => ts('Previous'),
898 );
899 }
900 if ($nextType != NULL) {
901 $nextButton = array(
902 'type' => $nextType,
903 'name' => $title,
904 'isDefault' => TRUE,
905 );
906 if ($submitOnce) {
907 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
908 }
909 $buttons[] = $nextButton;
910 }
911 $this->addButtons($buttons);
912 }
913
914 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
915 if ($displayTime) {
916 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
917 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
918 } else {
919 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
920 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
921 }
922 }
923
924 /**
925 * Adds a select based on field metadata
926 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
927 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
928 * @param $name - field name to go on the form
929 * @param array $props - mix of html attributes and special properties, namely
930 * - entity (api entity name, can usually be inferred automatically from the form class)
931 * - field (field name - only needed if different from name used on the form)
932 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
933 * - placeholder - set to NULL to disable
934 * - multiple - bool
935 * @param bool $required
936 * @throws CRM_Core_Exception
937 * @return HTML_QuickForm_Element
938 */
939 function addSelect($name, $props = array(), $required = FALSE) {
940 if (!isset($props['entity'])) {
941 $props['entity'] = CRM_Utils_Api::getEntityName($this);
942 }
943 if (!isset($props['field'])) {
944 $props['field'] = strrpos($name, '[') ? rtrim(substr($name, 1 + strrpos($name, '[')), ']') : $name;
945 }
946 $info = civicrm_api3($props['entity'], 'getoptions', array(
947 'field' => $props['field'],
948 'options' => array('metadata' => array('fields'))
949 )
950 );
951 $options = $info['values'];
952 if (!array_key_exists('placeholder', $props)) {
953 $props['placeholder'] = $required ? ts('- select -') : ts('- none -');
954 }
955 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
956 $options = array('' => '') + $options;
957 }
958 // Handle custom field
959 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
960 list(, $id) = explode('_', $name);
961 $label = isset($props['label']) ? $props['label'] : CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
962 $gid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
963 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : 'civicrm/admin/options/' . CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $gid);
964 }
965 // Core field
966 else {
967 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
968 if (
969 $uniqueName === $props['field'] ||
970 CRM_Utils_Array::value('name', $fieldSpec) === $props['field'] ||
971 in_array($props['field'], CRM_Utils_Array::value('api.aliases', $fieldSpec, array()))
972 ) {
973 break;
974 }
975 }
976 $label = isset($props['label']) ? $props['label'] : $fieldSpec['title'];
977 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec);
978 }
979 $props['class'] = (isset($props['class']) ? $props['class'] . ' ' : '') . "crm-select2";
980 $props['data-api-entity'] = $props['entity'];
981 $props['data-api-field'] = $props['field'];
982 CRM_Utils_Array::remove($props, 'label', 'entity', 'field', 'option_url');
983 return $this->add('select', $name, $label, $options, $required, $props);
984 }
985
986 /**
987 * Add a widget for selecting/editing/creating/copying a profile form
988 *
989 * @param string $name HTML form-element name
990 * @param string $label Printable label
991 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
992 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
993 * @param array $entities
994 */
995 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
996 // Output widget
997 // FIXME: Instead of adhoc serialization, use a single json_encode()
998 CRM_UF_Page_ProfileEditor::registerProfileScripts();
999 CRM_UF_Page_ProfileEditor::registerSchemas(CRM_Utils_Array::collect('entity_type', $entities));
1000 $this->add('text', $name, $label, array(
1001 'class' => 'crm-profile-selector',
1002 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1003 'data-group-type' => CRM_Core_BAO_UFGroup::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1004 'data-entities' => json_encode($entities),
1005 ));
1006 }
1007
1008 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1009 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1010 // 2. Based on the option, initialise proper editor
1011 $editorID = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
1012 'editor_id'
1013 );
1014 $editor = strtolower(CRM_Utils_Array::value($editorID,
1015 CRM_Core_OptionGroup::values('wysiwyg_editor')
1016 ));
1017 if (!$editor || $forceTextarea) {
1018 $editor = 'textarea';
1019 }
1020 if ($editor == 'joomla default editor') {
1021 $editor = 'joomlaeditor';
1022 }
1023
1024 if ($editor == 'drupal default editor') {
1025 $editor = 'drupalwysiwyg';
1026 }
1027
1028 //lets add the editor as a attribute
1029 $attributes['editor'] = $editor;
1030
1031 $this->addElement($editor, $name, $label, $attributes);
1032 $this->assign('editor', $editor);
1033
1034 // include wysiwyg editor js files
1035 // FIXME: This code does not make any sense
1036 $includeWysiwygEditor = FALSE;
1037 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1038 if (!$includeWysiwygEditor) {
1039 $includeWysiwygEditor = TRUE;
1040 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1041 }
1042
1043 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1044 }
1045
1046 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1047 $this->addElement('select', $id, $title,
1048 array(
1049 '' => ts('- select -')) + CRM_Core_PseudoConstant::country(), $extra
1050 );
1051 if ($required) {
1052 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1053 }
1054 }
1055
1056 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1057
1058 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1059
1060 if ($required) {
1061 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1062 }
1063 }
1064
1065 public function getRootTitle() {
1066 return NULL;
1067 }
1068
1069 public function getCompleteTitle() {
1070 return $this->getRootTitle() . $this->getTitle();
1071 }
1072
1073 static function &getTemplate() {
1074 return self::$_template;
1075 }
1076
1077 function addUploadElement($elementName) {
1078 $uploadNames = $this->get('uploadNames');
1079 if (!$uploadNames) {
1080 $uploadNames = array();
1081 }
1082 if (is_array($elementName)) {
1083 foreach ($elementName as $name) {
1084 if (!in_array($name, $uploadNames)) {
1085 $uploadNames[] = $name;
1086 }
1087 }
1088 }
1089 else {
1090 if (!in_array($elementName, $uploadNames)) {
1091 $uploadNames[] = $elementName;
1092 }
1093 }
1094 $this->set('uploadNames', $uploadNames);
1095
1096 $config = CRM_Core_Config::singleton();
1097 if (!empty($uploadNames)) {
1098 $this->controller->addUploadAction($config->customFileUploadDir, $uploadNames);
1099 }
1100 }
1101
1102 function buttonType() {
1103 $uploadNames = $this->get('uploadNames');
1104 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ? 'upload' : 'next';
1105 $this->assign('buttonType', $buttonType);
1106 return $buttonType;
1107 }
1108
1109 function getVar($name) {
1110 return isset($this->$name) ? $this->$name : NULL;
1111 }
1112
1113 function setVar($name, $value) {
1114 $this->$name = $value;
1115 }
1116
1117 /**
1118 * Function to add date
1119 * @param string $name name of the element
1120 * @param string $label label of the element
1121 * @param array $attributes key / value pair
1122 *
1123 // if you need time
1124 * $attributes = array ( 'addTime' => true,
1125 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1126 * );
1127 * @param boolean $required true if required
1128 *
1129 */
1130 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1131 if (!empty($attributes['formatType'])) {
1132 // get actual format
1133 $params = array('name' => $attributes['formatType']);
1134 $values = array();
1135
1136 // cache date information
1137 static $dateFormat;
1138 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1139 if (empty($dateFormat[$key])) {
1140 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1141 $dateFormat[$key] = $values;
1142 }
1143 else {
1144 $values = $dateFormat[$key];
1145 }
1146
1147 if ($values['date_format']) {
1148 $attributes['format'] = $values['date_format'];
1149 }
1150
1151 if (!empty($values['time_format'])) {
1152 $attributes['timeFormat'] = $values['time_format'];
1153 }
1154 $attributes['startOffset'] = $values['start'];
1155 $attributes['endOffset'] = $values['end'];
1156 }
1157
1158 $config = CRM_Core_Config::singleton();
1159 if (empty($attributes['format'])) {
1160 $attributes['format'] = $config->dateInputFormat;
1161 }
1162
1163 if (!isset($attributes['startOffset'])) {
1164 $attributes['startOffset'] = 10;
1165 }
1166
1167 if (!isset($attributes['endOffset'])) {
1168 $attributes['endOffset'] = 10;
1169 }
1170
1171 $this->add('text', $name, $label, $attributes);
1172
1173 if (!empty($attributes['addTime']) || !empty($attributes['timeFormat'])) {
1174
1175 if (!isset($attributes['timeFormat'])) {
1176 $timeFormat = $config->timeInputFormat;
1177 }
1178 else {
1179 $timeFormat = $attributes['timeFormat'];
1180 }
1181
1182 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1183 if ($timeFormat) {
1184 $show24Hours = TRUE;
1185 if ($timeFormat == 1) {
1186 $show24Hours = FALSE;
1187 }
1188
1189 //CRM-6664 -we are having time element name
1190 //in either flat string or an array format.
1191 $elementName = $name . '_time';
1192 if (substr($name, -1) == ']') {
1193 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1194 }
1195
1196 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1197 }
1198 }
1199
1200 if ($required) {
1201 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1202 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1203 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1204 }
1205 }
1206 }
1207
1208 /**
1209 * Function that will add date and time
1210 */
1211 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1212 $addTime = array('addTime' => TRUE);
1213 if (is_array($attributes)) {
1214 $attributes = array_merge($attributes, $addTime);
1215 }
1216 else {
1217 $attributes = $addTime;
1218 }
1219
1220 $this->addDate($name, $label, $required, $attributes);
1221 }
1222
1223 /**
1224 * add a currency and money element to the form
1225 */
1226 function addMoney($name,
1227 $label,
1228 $required = FALSE,
1229 $attributes = NULL,
1230 $addCurrency = TRUE,
1231 $currencyName = 'currency',
1232 $defaultCurrency = NULL,
1233 $freezeCurrency = FALSE
1234 ) {
1235 $element = $this->add('text', $name, $label, $attributes, $required);
1236 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1237
1238 if ($addCurrency) {
1239 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1240 }
1241
1242 return $element;
1243 }
1244
1245 /**
1246 * add currency element to the form
1247 */
1248 function addCurrency($name = 'currency',
1249 $label = NULL,
1250 $required = TRUE,
1251 $defaultCurrency = NULL,
1252 $freezeCurrency = FALSE
1253 ) {
1254 $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
1255 $options = array('class' => 'crm-select2 eight');
1256 if (!$required) {
1257 $currencies = array('' => '') + $currencies;
1258 $options['placeholder'] = ts('- none -');
1259 }
1260 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1261 if ($freezeCurrency) {
1262 $ele->freeze();
1263 }
1264 if (!$defaultCurrency) {
1265 $config = CRM_Core_Config::singleton();
1266 $defaultCurrency = $config->defaultCurrency;
1267 }
1268 $this->setDefaults(array($name => $defaultCurrency));
1269 }
1270
1271 /**
1272 * Create a single or multiple entity ref field
1273 * @param string $name
1274 * @param string $label
1275 * @param array $props mix of html and widget properties, including:
1276 * - select - params to give to select2 widget
1277 * - entity - defaults to contact
1278 * - create - can the user create a new entity on-the-fly?
1279 * Set to TRUE if entity is contact and you want the default profiles,
1280 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1281 * note that permissions are checked automatically
1282 * - api - array of settings for the getlist api wrapper
1283 * note that it accepts a 'params' setting which will be passed to the underlying api
1284 * - placeholder - string
1285 * - multiple - bool
1286 * - class, etc. - other html properties
1287 * @param bool $required
1288 *
1289 * @access public
1290 * @return HTML_QuickForm_Element
1291 */
1292 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1293 require_once "api/api.php";
1294 $config = CRM_Core_Config::singleton();
1295 // Default properties
1296 $props['api'] = CRM_Utils_Array::value('api', $props, array());
1297 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array::value('entity', $props, 'contact'));
1298 $props['class'] = ltrim(CRM_Utils_Array::value('class', $props, '') . ' crm-form-entityref');
1299
1300 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission::check('edit all contacts') || CRM_Core_Permission::check('add contacts'))) {
1301 unset($props['create']);
1302 }
1303
1304 $props['placeholder'] = CRM_Utils_Array::value('placeholder', $props, $required ? ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1305
1306 $defaults = array();
1307 if (!empty($props['multiple'])) {
1308 $defaults['multiple'] = TRUE;
1309 }
1310 $props['select'] = CRM_Utils_Array::value('select', $props, array()) + $defaults;
1311
1312 $this->formatReferenceFieldAttributes($props);
1313 return $this->add('text', $name, $label, $props, $required);
1314 }
1315
1316 /**
1317 * @param $props
1318 */
1319 private function formatReferenceFieldAttributes(&$props) {
1320 $props['data-select-params'] = json_encode($props['select']);
1321 $props['data-api-params'] = $props['api'] ? json_encode($props['api']) : NULL;
1322 $props['data-api-entity'] = $props['entity'];
1323 if (!empty($props['create'])) {
1324 $props['data-create-links'] = json_encode($props['create']);
1325 }
1326 CRM_Utils_Array::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1327 }
1328
1329 /**
1330 * Convert all date fields within the params to mysql date ready for the
1331 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1332 * and if time is defined it is incorporated
1333 *
1334 * @param array $params input params from the form
1335 *
1336 * @todo it would probably be better to work on $this->_params than a passed array
1337 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1338 * handling from BAO
1339 */
1340 function convertDateFieldsToMySQL(&$params){
1341 foreach ($this->_dateFields as $fieldName => $specs){
1342 if(!empty($params[$fieldName])){
1343 $params[$fieldName] = CRM_Utils_Date::isoToMysql(
1344 CRM_Utils_Date::processDate(
1345 $params[$fieldName],
1346 CRM_Utils_Array::value("{$fieldName}_time", $params), TRUE)
1347 );
1348 }
1349 else{
1350 if(isset($specs['default'])){
1351 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1352 }
1353 }
1354 }
1355 }
1356
1357 function removeFileRequiredRules($elementName) {
1358 $this->_required = array_diff($this->_required, array($elementName));
1359 if (isset($this->_rules[$elementName])) {
1360 foreach ($this->_rules[$elementName] as $index => $ruleInfo) {
1361 if ($ruleInfo['type'] == 'uploadedfile') {
1362 unset($this->_rules[$elementName][$index]);
1363 }
1364 }
1365 if (empty($this->_rules[$elementName])) {
1366 unset($this->_rules[$elementName]);
1367 }
1368 }
1369 }
1370
1371 /**
1372 * Function that can be defined in Form to override or
1373 * perform specific action on cancel action
1374 *
1375 * @access public
1376 */
1377 function cancelAction() {}
1378
1379 /**
1380 * Helper function to verify that required fields have been filled
1381 * Typically called within the scope of a FormRule function
1382 */
1383 static function validateMandatoryFields($fields, $values, &$errors) {
1384 foreach ($fields as $name => $fld) {
1385 if (!empty($fld['is_required']) && CRM_Utils_System::isNull(CRM_Utils_Array::value($name, $values))) {
1386 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1387 }
1388 }
1389 }
1390
1391 /**
1392 * Get contact if for a form object. Prioritise
1393 * - cid in URL if 0 (on behalf on someoneelse)
1394 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1395 * - logged in user id if it matches the one in the cid in the URL
1396 * - contact id validated from a checksum from a checksum
1397 * - cid from the url if the caller has ACL permission to view
1398 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1399 *
1400 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1401 */
1402 function getContactID() {
1403 $tempID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
1404 if(isset($this->_params) && isset($this->_params['select_contact_id'])) {
1405 $tempID = $this->_params['select_contact_id'];
1406 }
1407 if(isset($this->_params, $this->_params[0]) && !empty($this->_params[0]['select_contact_id'])) {
1408 // event form stores as an indexed array, contribution form not so much...
1409 $tempID = $this->_params[0]['select_contact_id'];
1410 }
1411
1412 // force to ignore the authenticated user
1413 if ($tempID === '0' || $tempID === 0) {
1414 // we set the cid on the form so that this will be retained for the Confirm page
1415 // in the multi-page form & prevent us returning the $userID when this is called
1416 // from that page
1417 // we don't really need to set it when $tempID is set because the params have that stored
1418 $this->set('cid', 0);
1419 return $tempID;
1420 }
1421
1422 $userID = $this->getLoggedInUserContactID();
1423
1424 if ($tempID == $userID) {
1425 return $userID;
1426 }
1427
1428 //check if this is a checksum authentication
1429 $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this);
1430 if ($userChecksum) {
1431 //check for anonymous user.
1432 $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($tempID, $userChecksum);
1433 if ($validUser) {
1434 return $tempID;
1435 }
1436 }
1437 // check if user has permission, CRM-12062
1438 else if ($tempID && CRM_Contact_BAO_Contact_Permission::allow($tempID)) {
1439 return $tempID;
1440 }
1441
1442 return $userID;
1443 }
1444
1445 /**
1446 * Get the contact id of the logged in user
1447 */
1448 function getLoggedInUserContactID() {
1449 // check if the user is logged in and has a contact ID
1450 $session = CRM_Core_Session::singleton();
1451 return $session->get('userID');
1452 }
1453
1454 /**
1455 * add autoselector field -if user has permission to view contacts
1456 * If adding this to a form you also need to add to the tpl e.g
1457 *
1458 * {if !empty($selectable)}
1459 * <div class="crm-summary-row">
1460 * <div class="crm-label">{$form.select_contact.label}</div>
1461 * <div class="crm-content">
1462 * {$form.select_contact.html}
1463 * </div>
1464 * </div>
1465 * {/if}
1466 *
1467 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1468 * @param array $autoCompleteField
1469 *
1470 * @internal param array $field metadata of field to use as selector including
1471 * - name_field
1472 * - id_field
1473 * - url (for ajax lookup)
1474 *
1475 * @todo add data attributes so we can deal with multiple instances on a form
1476 */
1477 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1478 $autoCompleteField = array_merge(array(
1479 'id_field' => 'select_contact_id',
1480 'placeholder' => ts('Select someone else ...'),
1481 'show_hide' => TRUE,
1482 'api' => array('params' => array('contact_type' => 'Individual'))
1483 ), $autoCompleteField);
1484
1485 if($this->canUseAjaxContactLookups()) {
1486 $this->assign('selectable', $autoCompleteField['id_field']);
1487 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1488
1489 CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1490 ->addSetting(array(
1491 'form' => array('autocompletes' => $autoCompleteField),
1492 'ids' => array('profile' => $profiles),
1493 ));
1494 }
1495 }
1496
1497 /**
1498 *
1499 */
1500 function canUseAjaxContactLookups() {
1501 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1502 CRM_Core_Permission::check(array(array('access AJAX API', 'access CiviCRM')))) {
1503 return TRUE;
1504 }
1505 }
1506
1507 /**
1508 * Add the options appropriate to cid = zero - ie. autocomplete
1509 *
1510 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1511 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1512 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1513 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1514 */
1515 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1516 $this->assign('nocid', TRUE);
1517 $profiles = array();
1518 if($this->_values['custom_pre_id']) {
1519 $profiles[] = $this->_values['custom_pre_id'];
1520 }
1521 if($this->_values['custom_post_id']) {
1522 $profiles[] = $this->_values['custom_post_id'];
1523 }
1524 if($onlinePaymentProcessorEnabled) {
1525 $profiles[] = 'billing';
1526 }
1527 if(!empty($this->_values)) {
1528 $this->addAutoSelector($profiles);
1529 }
1530 }
1531
1532 /**
1533 * Set default values on form for given contact (or no contact defaults)
1534 *
1535 * @param mixed $profile_id (can be id, or profile name)
1536 * @param integer $contactID
1537 *
1538 * @return array
1539 */
1540 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1541 try{
1542 $defaults = civicrm_api3('profile', 'getsingle', array(
1543 'profile_id' => (array) $profile_id,
1544 'contact_id' => $contactID,
1545 ));
1546 return $defaults;
1547 }
1548 catch (Exception $e) {
1549 // the try catch block gives us silent failure -not 100% sure this is a good idea
1550 // as silent failures are often worse than noisy ones
1551 return array();
1552 }
1553 }
1554
1555 /**
1556 * Sets form attribute
1557 * @see CRM.loadForm
1558 */
1559 function preventAjaxSubmit() {
1560 $this->setAttribute('data-no-ajax-submit', 'true');
1561 }
1562
1563 /**
1564 * Sets form attribute
1565 * @see CRM.loadForm
1566 */
1567 function allowAjaxSubmit() {
1568 $this->removeAttribute('data-no-ajax-submit');
1569 }
1570 }
1571