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