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