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