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