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