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