Merge pull request #3966 from eileenmcnaughton/CRM-15168-all-functions
[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 * @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 $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 $this->assign('translatePermission', CRM_Core_Permission::check('translate CiviCRM'));
439
440 if (
441 $this->controller->_key &&
442 $this->controller->_generateQFKey
443 ) {
444 $this->addElement('hidden', 'qfKey', $this->controller->_key);
445 $this->assign('qfKey', $this->controller->_key);
446
447 }
448
449 // _generateQFKey suppresses the qfKey generation on form snippets that
450 // are part of other forms, hence we use that to avoid adding entryURL
451 if ($this->controller->_generateQFKey && $this->controller->_entryURL) {
452 $this->addElement('hidden', 'entryURL', $this->controller->_entryURL);
453 }
454
455 $this->buildQuickForm();
456
457 $defaults = $this->setDefaultValues();
458 unset($defaults['qfKey']);
459
460 if (!empty($defaults)) {
461 $this->setDefaults($defaults);
462 }
463
464 // call the form hook
465 // also call the hook function so any modules can set thier own custom defaults
466 // the user can do both the form and set default values with this hook
467 CRM_Utils_Hook::buildForm(get_class($this), $this);
468
469 $this->addRules();
470
471 //Set html data-attribute to enable warning user of unsaved changes
472 if ($this->unsavedChangesWarn === true
473 || (!isset($this->unsavedChangesWarn)
474 && ($this->_action & CRM_Core_Action::ADD || $this->_action & CRM_Core_Action::UPDATE)
475 )
476 ) {
477 $this->setAttribute('data-warn-changes', 'true');
478 }
479 }
480
481 /**
482 * Add default Next / Back buttons
483 *
484 * @param array array of associative arrays in the order in which the buttons should be
485 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
486 * The base form class will define a bunch of static arrays for commonly used
487 * formats
488 *
489 * @return void
490 *
491 * @access public
492 *
493 */
494 function addButtons($params) {
495 $prevnext = array();
496 $spacing = array();
497 foreach ($params as $button) {
498 $js = CRM_Utils_Array::value('js', $button);
499 $isDefault = CRM_Utils_Array::value('isDefault', $button, FALSE);
500 if ($isDefault) {
501 $attrs = array('class' => 'crm-form-submit default');
502 }
503 else {
504 $attrs = array('class' => 'crm-form-submit');
505 }
506
507 if ($js) {
508 $attrs = array_merge($js, $attrs);
509 }
510
511 if ($button['type'] === 'cancel') {
512 $attrs['class'] .= ' cancel';
513 }
514
515 if ($button['type'] === 'reset') {
516 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
517 }
518 else {
519 if (!empty($button['subName'])) {
520 $buttonName = $this->getButtonName($button['type'], $button['subName']);
521 }
522 else {
523 $buttonName = $this->getButtonName($button['type']);
524 }
525
526 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
527 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
528 }
529 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
530 }
531 if (!empty($button['isDefault'])) {
532 $this->setDefaultAction($button['type']);
533 }
534
535 // if button type is upload, set the enctype
536 if ($button['type'] == 'upload') {
537 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
538 $this->setMaxFileSize();
539 }
540
541 // hack - addGroup uses an array to express variable spacing, read from the last element
542 $spacing[] = CRM_Utils_Array::value('spacing', $button, self::ATTR_SPACING);
543 }
544 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
545 }
546
547 /**
548 * getter function for Name
549 *
550 * @return string
551 * @access public
552 */
553 function getName() {
554 return $this->_name;
555 }
556
557 /**
558 * getter function for State
559 *
560 * @return object
561 * @access public
562 */
563 function &getState() {
564 return $this->_state;
565 }
566
567 /**
568 * getter function for StateType
569 *
570 * @return int
571 * @access public
572 */
573 function getStateType() {
574 return $this->_state->getType();
575 }
576
577 /**
578 * getter function for title. Should be over-ridden by derived class
579 *
580 * @return string
581 * @access public
582 */
583 function getTitle() {
584 return $this->_title ? $this->_title : ts('ERROR: Title is not Set');
585 }
586
587 /**
588 * setter function for title.
589 *
590 * @param string $title the title of the form
591 *
592 * @return void
593 * @access public
594 */
595 function setTitle($title) {
596 $this->_title = $title;
597 }
598
599 /**
600 * Setter function for options
601 *
602 * @param mixed
603 *
604 * @return void
605 * @access public
606 */
607 function setOptions($options) {
608 $this->_options = $options;
609 }
610
611 /**
612 * getter function for link.
613 *
614 * @return string
615 * @access public
616 */
617 function getLink() {
618 $config = CRM_Core_Config::singleton();
619 return CRM_Utils_System::url($_GET[$config->userFrameworkURLVar],
620 '_qf_' . $this->_name . '_display=true'
621 );
622 }
623
624 /**
625 * boolean function to determine if this is a one form page
626 *
627 * @return boolean
628 * @access public
629 */
630 function isSimpleForm() {
631 return $this->_state->getType() & (CRM_Core_State::START | CRM_Core_State::FINISH);
632 }
633
634 /**
635 * getter function for Form Action
636 *
637 * @return string
638 * @access public
639 */
640 function getFormAction() {
641 return $this->_attributes['action'];
642 }
643
644 /**
645 * setter function for Form Action
646 *
647 * @param string
648 *
649 * @return void
650 * @access public
651 */
652 function setFormAction($action) {
653 $this->_attributes['action'] = $action;
654 }
655
656 /**
657 * render form and return contents
658 *
659 * @return string
660 * @access public
661 */
662 function toSmarty() {
663 $this->preProcessChainSelectFields();
664 $renderer = $this->getRenderer();
665 $this->accept($renderer);
666 $content = $renderer->toArray();
667 $content['formName'] = $this->getName();
668 // CRM-15153
669 $content['formClass'] = CRM_Utils_System::getClassName($this);
670 return $content;
671 }
672
673 /**
674 * getter function for renderer. If renderer is not set
675 * create one and initialize it
676 *
677 * @return object
678 * @access public
679 */
680 function &getRenderer() {
681 if (!isset($this->_renderer)) {
682 $this->_renderer = CRM_Core_Form_Renderer::singleton();
683 }
684 return $this->_renderer;
685 }
686
687 /**
688 * Use the form name to create the tpl file name
689 *
690 * @return string
691 * @access public
692 */
693 function getTemplateFileName() {
694 $ext = CRM_Extension_System::singleton()->getMapper();
695 if ($ext->isExtensionClass(CRM_Utils_System::getClassName($this))) {
696 $filename = $ext->getTemplateName(CRM_Utils_System::getClassName($this));
697 $tplname = $ext->getTemplatePath(CRM_Utils_System::getClassName($this)) . DIRECTORY_SEPARATOR . $filename;
698 }
699 else {
700 $tplname = str_replace('_',
701 DIRECTORY_SEPARATOR,
702 CRM_Utils_System::getClassName($this)
703 ) . '.tpl';
704 }
705 return $tplname;
706 }
707
708 /**
709 * A wrapper for getTemplateFileName that includes calling the hook to
710 * prevent us from having to copy & paste the logic of calling the hook
711 */
712 function getHookedTemplateFileName() {
713 $pageTemplateFile = $this->getTemplateFileName();
714 CRM_Utils_Hook::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
715 return $pageTemplateFile;
716 }
717
718 /**
719 * Default extra tpl file basically just replaces .tpl with .extra.tpl
720 * i.e. we dont override
721 *
722 * @return string
723 * @access public
724 */
725 function overrideExtraTemplateFileName() {
726 return NULL;
727 }
728
729 /**
730 * Error reporting mechanism
731 *
732 * @param string $message Error Message
733 * @param int $code Error Code
734 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
735 *
736 * @return void
737 * @access public
738 */
739 function error($message, $code = NULL, $dao = NULL) {
740 if ($dao) {
741 $dao->query('ROLLBACK');
742 }
743
744 $error = CRM_Core_Error::singleton();
745
746 $error->push($code, $message);
747 }
748
749 /**
750 * Store the variable with the value in the form scope
751 *
752 * @param string name : name of the variable
753 * @param mixed value : value of the variable
754 *
755 * @access public
756 *
757 * @return void
758 *
759 */
760 function set($name, $value) {
761 $this->controller->set($name, $value);
762 }
763
764 /**
765 * Get the variable from the form scope
766 *
767 * @param string name : name of the variable
768 *
769 * @access public
770 *
771 * @return mixed
772 *
773 */
774 function get($name) {
775 return $this->controller->get($name);
776 }
777
778 /**
779 * getter for action
780 *
781 * @return int
782 * @access public
783 */
784 function getAction() {
785 return $this->_action;
786 }
787
788 /**
789 * setter for action
790 *
791 * @param int $action the mode we want to set the form
792 *
793 * @return void
794 * @access public
795 */
796 function setAction($action) {
797 $this->_action = $action;
798 }
799
800 /**
801 * assign value to name in template
802 *
803 * @param $var
804 * @param mixed $value value of varaible
805 *
806 * @internal param array|string $name name of variable
807 * @return void
808 * @access public
809 */
810 function assign($var, $value = NULL) {
811 self::$_template->assign($var, $value);
812 }
813
814 /**
815 * assign value to name in template by reference
816 *
817 * @param $var
818 * @param mixed $value value of varaible
819 *
820 * @internal param array|string $name name of variable
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 * @internal param string $type
845 * @return array
846 */
847 function get_template_vars($name=null) {
848 return self::$_template->get_template_vars($name);
849 }
850
851 /**
852 * @param $name
853 * @param $title
854 * @param $values
855 * @param array $attributes
856 * @param null $separator
857 * @param bool $required
858 *
859 * @return HTML_QuickForm_group
860 */
861 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
862 $options = array();
863 $attributes = $attributes ? $attributes : array();
864 $allowClear = !empty($attributes['allowClear']);
865 unset($attributes['allowClear']);
866 $attributes += array('id_suffix' => $name);
867 foreach ($values as $key => $var) {
868 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
869 }
870 $group = $this->addGroup($options, $name, $title, $separator);
871 if ($required) {
872 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
873 }
874 if ($allowClear) {
875 $group->setAttribute('allowClear', TRUE);
876 }
877 return $group;
878 }
879
880 /**
881 * @param $id
882 * @param $title
883 * @param bool $allowClear
884 * @param null $required
885 * @param array $attributes
886 */
887 function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
888 $attributes += array('id_suffix' => $id);
889 $choice = array();
890 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
891 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
892
893 $group = $this->addGroup($choice, $id, $title);
894 if ($allowClear) {
895 $group->setAttribute('allowClear', TRUE);
896 }
897 if ($required) {
898 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
899 }
900 }
901
902 /**
903 * @param $id
904 * @param $title
905 * @param $values
906 * @param null $other
907 * @param null $attributes
908 * @param null $required
909 * @param null $javascriptMethod
910 * @param string $separator
911 * @param bool $flipValues
912 */
913 function addCheckBox($id, $title, $values, $other = NULL,
914 $attributes = NULL, $required = NULL,
915 $javascriptMethod = NULL,
916 $separator = '<br />', $flipValues = FALSE
917 ) {
918 $options = array();
919
920 if ($javascriptMethod) {
921 foreach ($values as $key => $var) {
922 if (!$flipValues) {
923 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
924 }
925 else {
926 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
927 }
928 }
929 }
930 else {
931 foreach ($values as $key => $var) {
932 if (!$flipValues) {
933 $options[] = $this->createElement('checkbox', $var, NULL, $key);
934 }
935 else {
936 $options[] = $this->createElement('checkbox', $key, NULL, $var);
937 }
938 }
939 }
940
941 $this->addGroup($options, $id, $title, $separator);
942
943 if ($other) {
944 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
945 }
946
947 if ($required) {
948 $this->addRule($id,
949 ts('%1 is a required field.', array(1 => $title)),
950 'required'
951 );
952 }
953 }
954
955 function resetValues() {
956 $data = $this->controller->container();
957 $data['values'][$this->_name] = array();
958 }
959
960 /**
961 * simple shell that derived classes can call to add buttons to
962 * the form with a customized title for the main Submit
963 *
964 * @param string $title title of the main button
965 * @param string $nextType
966 * @param string $backType
967 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
968 *
969 * @internal param string $type button type for the form after processing
970 * @return void
971 * @access public
972 */
973 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
974 $buttons = array();
975 if ($backType != NULL) {
976 $buttons[] = array(
977 'type' => $backType,
978 'name' => ts('Previous'),
979 );
980 }
981 if ($nextType != NULL) {
982 $nextButton = array(
983 'type' => $nextType,
984 'name' => $title,
985 'isDefault' => TRUE,
986 );
987 if ($submitOnce) {
988 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
989 }
990 $buttons[] = $nextButton;
991 }
992 $this->addButtons($buttons);
993 }
994
995 /**
996 * @param $name
997 * @param string $from
998 * @param string $to
999 * @param string $label
1000 * @param string $dateFormat
1001 * @param bool $required
1002 * @param bool $displayTime
1003 */
1004 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
1005 if ($displayTime) {
1006 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
1007 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1008 } else {
1009 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
1010 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1011 }
1012 }
1013
1014 /**
1015 * Adds a select based on field metadata
1016 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
1017 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
1018 * @param $name - field name to go on the form
1019 * @param array $props - mix of html attributes and special properties, namely
1020 * - entity (api entity name, can usually be inferred automatically from the form class)
1021 * - field (field name - only needed if different from name used on the form)
1022 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
1023 * - placeholder - set to NULL to disable
1024 * - multiple - bool
1025 * @param bool $required
1026 * @throws CRM_Core_Exception
1027 * @return HTML_QuickForm_Element
1028 */
1029 function addSelect($name, $props = array(), $required = FALSE) {
1030 if (!isset($props['entity'])) {
1031 $props['entity'] = CRM_Utils_Api::getEntityName($this);
1032 }
1033 if (!isset($props['field'])) {
1034 $props['field'] = strrpos($name, '[') ? rtrim(substr($name, 1 + strrpos($name, '[')), ']') : $name;
1035 }
1036 $info = civicrm_api3($props['entity'], 'getoptions', array(
1037 'field' => $props['field'],
1038 'options' => array('metadata' => array('fields'))
1039 )
1040 );
1041 $options = $info['values'];
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 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
1055 if (
1056 $uniqueName === $props['field'] ||
1057 CRM_Utils_Array::value('name', $fieldSpec) === $props['field'] ||
1058 in_array($props['field'], CRM_Utils_Array::value('api.aliases', $fieldSpec, array()))
1059 ) {
1060 break;
1061 }
1062 }
1063 $label = isset($props['label']) ? $props['label'] : $fieldSpec['title'];
1064 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec);
1065 }
1066 $props['class'] = (isset($props['class']) ? $props['class'] . ' ' : '') . "crm-select2";
1067 $props['data-api-entity'] = $props['entity'];
1068 $props['data-api-field'] = $props['field'];
1069 CRM_Utils_Array::remove($props, 'label', 'entity', 'field', 'option_url');
1070 return $this->add('select', $name, $label, $options, $required, $props);
1071 }
1072
1073 /**
1074 * Add a widget for selecting/editing/creating/copying a profile form
1075 *
1076 * @param string $name HTML form-element name
1077 * @param string $label Printable label
1078 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
1079 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
1080 * @param array $entities
1081 */
1082 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
1083 // Output widget
1084 // FIXME: Instead of adhoc serialization, use a single json_encode()
1085 CRM_UF_Page_ProfileEditor::registerProfileScripts();
1086 CRM_UF_Page_ProfileEditor::registerSchemas(CRM_Utils_Array::collect('entity_type', $entities));
1087 $this->add('text', $name, $label, array(
1088 'class' => 'crm-profile-selector',
1089 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1090 'data-group-type' => CRM_Core_BAO_UFGroup::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1091 'data-entities' => json_encode($entities),
1092 ));
1093 }
1094
1095 /**
1096 * @param $name
1097 * @param $label
1098 * @param $attributes
1099 * @param bool $forceTextarea
1100 */
1101 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1102 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1103 // 2. Based on the option, initialise proper editor
1104 $editorID = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
1105 'editor_id'
1106 );
1107 $editor = strtolower(CRM_Utils_Array::value($editorID,
1108 CRM_Core_OptionGroup::values('wysiwyg_editor')
1109 ));
1110 if (!$editor || $forceTextarea) {
1111 $editor = 'textarea';
1112 }
1113 if ($editor == 'joomla default editor') {
1114 $editor = 'joomlaeditor';
1115 }
1116
1117 if ($editor == 'drupal default editor') {
1118 $editor = 'drupalwysiwyg';
1119 }
1120
1121 //lets add the editor as a attribute
1122 $attributes['editor'] = $editor;
1123
1124 $this->addElement($editor, $name, $label, $attributes);
1125 $this->assign('editor', $editor);
1126
1127 // include wysiwyg editor js files
1128 // FIXME: This code does not make any sense
1129 $includeWysiwygEditor = FALSE;
1130 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1131 if (!$includeWysiwygEditor) {
1132 $includeWysiwygEditor = TRUE;
1133 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1134 }
1135
1136 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1137 }
1138
1139 /**
1140 * @param $id
1141 * @param $title
1142 * @param null $required
1143 * @param null $extra
1144 */
1145 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1146 $this->addElement('select', $id, $title,
1147 array(
1148 '' => ts('- select -')) + CRM_Core_PseudoConstant::country(), $extra
1149 );
1150 if ($required) {
1151 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1152 }
1153 }
1154
1155 /**
1156 * @param $name
1157 * @param $label
1158 * @param $options
1159 * @param $attributes
1160 * @param null $required
1161 * @param null $javascriptMethod
1162 */
1163 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1164
1165 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1166
1167 if ($required) {
1168 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1169 }
1170 }
1171
1172 /**
1173 * @return null
1174 */
1175 public function getRootTitle() {
1176 return NULL;
1177 }
1178
1179 /**
1180 * @return string
1181 */
1182 public function getCompleteTitle() {
1183 return $this->getRootTitle() . $this->getTitle();
1184 }
1185
1186 /**
1187 * @return CRM_Core_Smarty
1188 */
1189 static function &getTemplate() {
1190 return self::$_template;
1191 }
1192
1193 /**
1194 * @param $elementName
1195 */
1196 function addUploadElement($elementName) {
1197 $uploadNames = $this->get('uploadNames');
1198 if (!$uploadNames) {
1199 $uploadNames = array();
1200 }
1201 if (is_array($elementName)) {
1202 foreach ($elementName as $name) {
1203 if (!in_array($name, $uploadNames)) {
1204 $uploadNames[] = $name;
1205 }
1206 }
1207 }
1208 else {
1209 if (!in_array($elementName, $uploadNames)) {
1210 $uploadNames[] = $elementName;
1211 }
1212 }
1213 $this->set('uploadNames', $uploadNames);
1214
1215 $config = CRM_Core_Config::singleton();
1216 if (!empty($uploadNames)) {
1217 $this->controller->addUploadAction($config->customFileUploadDir, $uploadNames);
1218 }
1219 }
1220
1221 /**
1222 * @return string
1223 */
1224 function buttonType() {
1225 $uploadNames = $this->get('uploadNames');
1226 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ? 'upload' : 'next';
1227 $this->assign('buttonType', $buttonType);
1228 return $buttonType;
1229 }
1230
1231 /**
1232 * @param $name
1233 *
1234 * @return null
1235 */
1236 function getVar($name) {
1237 return isset($this->$name) ? $this->$name : NULL;
1238 }
1239
1240 /**
1241 * @param $name
1242 * @param $value
1243 */
1244 function setVar($name, $value) {
1245 $this->$name = $value;
1246 }
1247
1248 /**
1249 * Function to add date
1250 * @param string $name name of the element
1251 * @param string $label label of the element
1252 * @param array $attributes key / value pair
1253 *
1254 // if you need time
1255 * $attributes = array ( 'addTime' => true,
1256 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1257 * );
1258 * @param boolean $required true if required
1259 *
1260 */
1261 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1262 if (!empty($attributes['formatType'])) {
1263 // get actual format
1264 $params = array('name' => $attributes['formatType']);
1265 $values = array();
1266
1267 // cache date information
1268 static $dateFormat;
1269 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1270 if (empty($dateFormat[$key])) {
1271 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1272 $dateFormat[$key] = $values;
1273 }
1274 else {
1275 $values = $dateFormat[$key];
1276 }
1277
1278 if ($values['date_format']) {
1279 $attributes['format'] = $values['date_format'];
1280 }
1281
1282 if (!empty($values['time_format'])) {
1283 $attributes['timeFormat'] = $values['time_format'];
1284 }
1285 $attributes['startOffset'] = $values['start'];
1286 $attributes['endOffset'] = $values['end'];
1287 }
1288
1289 $config = CRM_Core_Config::singleton();
1290 if (empty($attributes['format'])) {
1291 $attributes['format'] = $config->dateInputFormat;
1292 }
1293
1294 if (!isset($attributes['startOffset'])) {
1295 $attributes['startOffset'] = 10;
1296 }
1297
1298 if (!isset($attributes['endOffset'])) {
1299 $attributes['endOffset'] = 10;
1300 }
1301
1302 $this->add('text', $name, $label, $attributes);
1303
1304 if (!empty($attributes['addTime']) || !empty($attributes['timeFormat'])) {
1305
1306 if (!isset($attributes['timeFormat'])) {
1307 $timeFormat = $config->timeInputFormat;
1308 }
1309 else {
1310 $timeFormat = $attributes['timeFormat'];
1311 }
1312
1313 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1314 if ($timeFormat) {
1315 $show24Hours = TRUE;
1316 if ($timeFormat == 1) {
1317 $show24Hours = FALSE;
1318 }
1319
1320 //CRM-6664 -we are having time element name
1321 //in either flat string or an array format.
1322 $elementName = $name . '_time';
1323 if (substr($name, -1) == ']') {
1324 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1325 }
1326
1327 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1328 }
1329 }
1330
1331 if ($required) {
1332 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1333 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1334 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1335 }
1336 }
1337 }
1338
1339 /**
1340 * Function that will add date and time
1341 */
1342 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1343 $addTime = array('addTime' => TRUE);
1344 if (is_array($attributes)) {
1345 $attributes = array_merge($attributes, $addTime);
1346 }
1347 else {
1348 $attributes = $addTime;
1349 }
1350
1351 $this->addDate($name, $label, $required, $attributes);
1352 }
1353
1354 /**
1355 * add a currency and money element to the form
1356 */
1357 function addMoney($name,
1358 $label,
1359 $required = FALSE,
1360 $attributes = NULL,
1361 $addCurrency = TRUE,
1362 $currencyName = 'currency',
1363 $defaultCurrency = NULL,
1364 $freezeCurrency = FALSE
1365 ) {
1366 $element = $this->add('text', $name, $label, $attributes, $required);
1367 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1368
1369 if ($addCurrency) {
1370 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1371 }
1372
1373 return $element;
1374 }
1375
1376 /**
1377 * add currency element to the form
1378 */
1379 function addCurrency($name = 'currency',
1380 $label = NULL,
1381 $required = TRUE,
1382 $defaultCurrency = NULL,
1383 $freezeCurrency = FALSE
1384 ) {
1385 $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
1386 $options = array('class' => 'crm-select2 eight');
1387 if (!$required) {
1388 $currencies = array('' => '') + $currencies;
1389 $options['placeholder'] = ts('- none -');
1390 }
1391 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1392 if ($freezeCurrency) {
1393 $ele->freeze();
1394 }
1395 if (!$defaultCurrency) {
1396 $config = CRM_Core_Config::singleton();
1397 $defaultCurrency = $config->defaultCurrency;
1398 }
1399 $this->setDefaults(array($name => $defaultCurrency));
1400 }
1401
1402 /**
1403 * Create a single or multiple entity ref field
1404 * @param string $name
1405 * @param string $label
1406 * @param array $props mix of html and widget properties, including:
1407 * - select - params to give to select2 widget
1408 * - entity - defaults to contact
1409 * - create - can the user create a new entity on-the-fly?
1410 * Set to TRUE if entity is contact and you want the default profiles,
1411 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1412 * note that permissions are checked automatically
1413 * - api - array of settings for the getlist api wrapper
1414 * note that it accepts a 'params' setting which will be passed to the underlying api
1415 * - placeholder - string
1416 * - multiple - bool
1417 * - class, etc. - other html properties
1418 * @param bool $required
1419 *
1420 * @access public
1421 * @return HTML_QuickForm_Element
1422 */
1423 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1424 require_once "api/api.php";
1425 $config = CRM_Core_Config::singleton();
1426 // Default properties
1427 $props['api'] = CRM_Utils_Array::value('api', $props, array());
1428 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array::value('entity', $props, 'contact'));
1429 $props['class'] = ltrim(CRM_Utils_Array::value('class', $props, '') . ' crm-form-entityref');
1430
1431 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission::check('edit all contacts') || CRM_Core_Permission::check('add contacts'))) {
1432 unset($props['create']);
1433 }
1434
1435 $props['placeholder'] = CRM_Utils_Array::value('placeholder', $props, $required ? ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1436
1437 $defaults = array();
1438 if (!empty($props['multiple'])) {
1439 $defaults['multiple'] = TRUE;
1440 }
1441 $props['select'] = CRM_Utils_Array::value('select', $props, array()) + $defaults;
1442
1443 $this->formatReferenceFieldAttributes($props);
1444 return $this->add('text', $name, $label, $props, $required);
1445 }
1446
1447 /**
1448 * @param $props
1449 */
1450 private function formatReferenceFieldAttributes(&$props) {
1451 $props['data-select-params'] = json_encode($props['select']);
1452 $props['data-api-params'] = $props['api'] ? json_encode($props['api']) : NULL;
1453 $props['data-api-entity'] = $props['entity'];
1454 if (!empty($props['create'])) {
1455 $props['data-create-links'] = json_encode($props['create']);
1456 }
1457 CRM_Utils_Array::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1458 }
1459
1460 /**
1461 * Convert all date fields within the params to mysql date ready for the
1462 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1463 * and if time is defined it is incorporated
1464 *
1465 * @param array $params input params from the form
1466 *
1467 * @todo it would probably be better to work on $this->_params than a passed array
1468 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1469 * handling from BAO
1470 */
1471 function convertDateFieldsToMySQL(&$params){
1472 foreach ($this->_dateFields as $fieldName => $specs){
1473 if(!empty($params[$fieldName])){
1474 $params[$fieldName] = CRM_Utils_Date::isoToMysql(
1475 CRM_Utils_Date::processDate(
1476 $params[$fieldName],
1477 CRM_Utils_Array::value("{$fieldName}_time", $params), TRUE)
1478 );
1479 }
1480 else{
1481 if(isset($specs['default'])){
1482 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1483 }
1484 }
1485 }
1486 }
1487
1488 /**
1489 * @param $elementName
1490 */
1491 function removeFileRequiredRules($elementName) {
1492 $this->_required = array_diff($this->_required, array($elementName));
1493 if (isset($this->_rules[$elementName])) {
1494 foreach ($this->_rules[$elementName] as $index => $ruleInfo) {
1495 if ($ruleInfo['type'] == 'uploadedfile') {
1496 unset($this->_rules[$elementName][$index]);
1497 }
1498 }
1499 if (empty($this->_rules[$elementName])) {
1500 unset($this->_rules[$elementName]);
1501 }
1502 }
1503 }
1504
1505 /**
1506 * Function that can be defined in Form to override or
1507 * perform specific action on cancel action
1508 *
1509 * @access public
1510 */
1511 function cancelAction() {}
1512
1513 /**
1514 * Helper function to verify that required fields have been filled
1515 * Typically called within the scope of a FormRule function
1516 */
1517 static function validateMandatoryFields($fields, $values, &$errors) {
1518 foreach ($fields as $name => $fld) {
1519 if (!empty($fld['is_required']) && CRM_Utils_System::isNull(CRM_Utils_Array::value($name, $values))) {
1520 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1521 }
1522 }
1523 }
1524
1525 /**
1526 * Get contact if for a form object. Prioritise
1527 * - cid in URL if 0 (on behalf on someoneelse)
1528 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1529 * - logged in user id if it matches the one in the cid in the URL
1530 * - contact id validated from a checksum from a checksum
1531 * - cid from the url if the caller has ACL permission to view
1532 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1533 *
1534 * @return mixed NULL|integer
1535 */
1536 function getContactID() {
1537 $tempID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
1538 if(isset($this->_params) && isset($this->_params['select_contact_id'])) {
1539 $tempID = $this->_params['select_contact_id'];
1540 }
1541 if(isset($this->_params, $this->_params[0]) && !empty($this->_params[0]['select_contact_id'])) {
1542 // event form stores as an indexed array, contribution form not so much...
1543 $tempID = $this->_params[0]['select_contact_id'];
1544 }
1545
1546 // force to ignore the authenticated user
1547 if ($tempID === '0' || $tempID === 0) {
1548 // we set the cid on the form so that this will be retained for the Confirm page
1549 // in the multi-page form & prevent us returning the $userID when this is called
1550 // from that page
1551 // we don't really need to set it when $tempID is set because the params have that stored
1552 $this->set('cid', 0);
1553 return $tempID;
1554 }
1555
1556 $userID = $this->getLoggedInUserContactID();
1557
1558 if ($tempID == $userID) {
1559 return $userID;
1560 }
1561
1562 //check if this is a checksum authentication
1563 $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this);
1564 if ($userChecksum) {
1565 //check for anonymous user.
1566 $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($tempID, $userChecksum);
1567 if ($validUser) {
1568 return $tempID;
1569 }
1570 }
1571 // check if user has permission, CRM-12062
1572 else if ($tempID && CRM_Contact_BAO_Contact_Permission::allow($tempID)) {
1573 return $tempID;
1574 }
1575
1576 return $userID;
1577 }
1578
1579 /**
1580 * Get the contact id of the logged in user
1581 */
1582 function getLoggedInUserContactID() {
1583 // check if the user is logged in and has a contact ID
1584 $session = CRM_Core_Session::singleton();
1585 return $session->get('userID');
1586 }
1587
1588 /**
1589 * add autoselector field -if user has permission to view contacts
1590 * If adding this to a form you also need to add to the tpl e.g
1591 *
1592 * {if !empty($selectable)}
1593 * <div class="crm-summary-row">
1594 * <div class="crm-label">{$form.select_contact.label}</div>
1595 * <div class="crm-content">
1596 * {$form.select_contact.html}
1597 * </div>
1598 * </div>
1599 * {/if}
1600 *
1601 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1602 * @param array $autoCompleteField
1603 *
1604 * @internal param array $field metadata of field to use as selector including
1605 * - name_field
1606 * - id_field
1607 * - url (for ajax lookup)
1608 *
1609 * @todo add data attributes so we can deal with multiple instances on a form
1610 */
1611 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1612 $autoCompleteField = array_merge(array(
1613 'id_field' => 'select_contact_id',
1614 'placeholder' => ts('Select someone else ...'),
1615 'show_hide' => TRUE,
1616 'api' => array('params' => array('contact_type' => 'Individual'))
1617 ), $autoCompleteField);
1618
1619 if($this->canUseAjaxContactLookups()) {
1620 $this->assign('selectable', $autoCompleteField['id_field']);
1621 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1622
1623 CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1624 ->addSetting(array(
1625 'form' => array('autocompletes' => $autoCompleteField),
1626 'ids' => array('profile' => $profiles),
1627 ));
1628 }
1629 }
1630
1631 /**
1632 *
1633 */
1634 function canUseAjaxContactLookups() {
1635 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1636 CRM_Core_Permission::check(array(array('access AJAX API', 'access CiviCRM')))) {
1637 return TRUE;
1638 }
1639 }
1640
1641 /**
1642 * Add the options appropriate to cid = zero - ie. autocomplete
1643 *
1644 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1645 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1646 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1647 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1648 */
1649 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1650 $this->assign('nocid', TRUE);
1651 $profiles = array();
1652 if($this->_values['custom_pre_id']) {
1653 $profiles[] = $this->_values['custom_pre_id'];
1654 }
1655 if($this->_values['custom_post_id']) {
1656 $profiles[] = $this->_values['custom_post_id'];
1657 }
1658 if($onlinePaymentProcessorEnabled) {
1659 $profiles[] = 'billing';
1660 }
1661 if(!empty($this->_values)) {
1662 $this->addAutoSelector($profiles);
1663 }
1664 }
1665
1666 /**
1667 * Set default values on form for given contact (or no contact defaults)
1668 *
1669 * @param mixed $profile_id (can be id, or profile name)
1670 * @param integer $contactID
1671 *
1672 * @return array
1673 */
1674 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1675 try{
1676 $defaults = civicrm_api3('profile', 'getsingle', array(
1677 'profile_id' => (array) $profile_id,
1678 'contact_id' => $contactID,
1679 ));
1680 return $defaults;
1681 }
1682 catch (Exception $e) {
1683 // the try catch block gives us silent failure -not 100% sure this is a good idea
1684 // as silent failures are often worse than noisy ones
1685 return array();
1686 }
1687 }
1688
1689 /**
1690 * Sets form attribute
1691 * @see CRM.loadForm
1692 */
1693 function preventAjaxSubmit() {
1694 $this->setAttribute('data-no-ajax-submit', 'true');
1695 }
1696
1697 /**
1698 * Sets form attribute
1699 * @see CRM.loadForm
1700 */
1701 function allowAjaxSubmit() {
1702 $this->removeAttribute('data-no-ajax-submit');
1703 }
1704
1705 /**
1706 * Sets page title based on entity and action
1707 * @param string $entityLabel
1708 */
1709 function setPageTitle($entityLabel) {
1710 switch ($this->_action) {
1711 case CRM_Core_Action::ADD:
1712 CRM_Utils_System::setTitle(ts('New %1', array(1 => $entityLabel)));
1713 break;
1714 case CRM_Core_Action::UPDATE:
1715 CRM_Utils_System::setTitle(ts('Edit %1', array(1 => $entityLabel)));
1716 break;
1717 case CRM_Core_Action::VIEW:
1718 case CRM_Core_Action::PREVIEW:
1719 CRM_Utils_System::setTitle(ts('View %1', array(1 => $entityLabel)));
1720 break;
1721 case CRM_Core_Action::DELETE:
1722 CRM_Utils_System::setTitle(ts('Delete %1', array(1 => $entityLabel)));
1723 break;
1724 }
1725 }
1726
1727 /**
1728 * Create a chain-select target field. All settings are optional; the defaults usually work.
1729 *
1730 * @param string $elementName
1731 * @param array $settings
1732 *
1733 * @return HTML_QuickForm_Element
1734 */
1735 public function addChainSelect($elementName, $settings = array()) {
1736 $props = $settings += array(
1737 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array('country', 'Country', 'state_province', 'StateProvince'), $elementName),
1738 'data-callback' => strpos($elementName, 'rovince') ? 'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
1739 'label' => strpos($elementName, 'rovince') ? ts('State / Province') : ts('County'),
1740 'data-empty-prompt' => strpos($elementName, 'rovince') ? ts('Choose country first') : ts('Choose state first'),
1741 'data-none-prompt' => ts('- N/A -'),
1742 'multiple' => FALSE,
1743 'required' => FALSE,
1744 'placeholder' => empty($settings['required']) ? ts('- none -') : ts('- select -'),
1745 );
1746 CRM_Utils_Array::remove($props, 'label', 'required', 'control_field');
1747 $props['class'] = (empty($props['class']) ? '' : "{$props['class']} ") . 'crm-chain-select-target crm-select2';
1748 $props['data-select-prompt'] = $props['placeholder'];
1749 $props['data-name'] = $elementName;
1750
1751 $this->_chainSelectFields[$settings['control_field']] = $elementName;
1752
1753 return $this->add('select', $elementName, $settings['label'], array(), $settings['required'], $props);
1754 }
1755
1756 /**
1757 * Set options and attributes for chain select fields based on the controlling field's value
1758 */
1759 private function preProcessChainSelectFields() {
1760 foreach ($this->_chainSelectFields as $control => $target) {
1761 $controlField = $this->getElement($control);
1762 $targetField = $this->getElement($target);
1763 $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'stateProvince' : 'country';
1764
1765 $css = (string) $controlField->getAttribute('class');
1766 $controlField->updateAttributes(array(
1767 'class' => ($css ? "$css " : 'crm-select2 ') . 'crm-chain-select-control',
1768 'data-target' => $target,
1769 ));
1770 $controlValue = $controlField->getValue();
1771 $options = array();
1772 if ($controlValue) {
1773 $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
1774 if (!$options) {
1775 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
1776 }
1777 }
1778 else {
1779 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
1780 $targetField->setAttribute('disabled', 'disabled');
1781 }
1782 if (!$targetField->getAttribute('multiple')) {
1783 $options = array('' => $targetField->getAttribute('placeholder')) + $options;
1784 $targetField->removeAttribute('placeholder');
1785 }
1786 $targetField->loadArray($options);
1787 }
1788 }
1789
1790 /**
1791 * Validate country / state / county match and suppress unwanted "required" errors
1792 */
1793 private function validateChainSelectFields() {
1794 foreach ($this->_chainSelectFields as $control => $target) {
1795 $controlValue = (array) $this->getElementValue($control);
1796 $targetField = $this->getElement($target);
1797 $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'stateProvince' : 'country';
1798 $targetValue = array_filter((array) $targetField->getValue());
1799 if ($targetValue || $this->getElementError($target)) {
1800 $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
1801 if ($targetValue) {
1802 if (!array_intersect($targetValue, array_keys($options))) {
1803 $this->setElementError($target, $controlType == 'country' ? ts('State/Province does not match the selected Country') : ts('County does not match the selected State/Province'));
1804 }
1805 }
1806 // Suppress "required" error for field if it has no options
1807 elseif (!$options) {
1808 $this->setElementError($target, NULL);
1809 }
1810 }
1811 }
1812 }
1813 }
1814