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