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