INFRA-132 - CRM/Core - Misc
[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 } else {
1000 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
1001 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1002 }
1003 }
1004
1005 /**
1006 * Adds a select based on field metadata
1007 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
1008 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
1009 * @param $name
1010 * Field name to go on the form.
1011 * @param array $props
1012 * Mix of html attributes and special properties, namely.
1013 * - entity (api entity name, can usually be inferred automatically from the form class)
1014 * - field (field name - only needed if different from name used on the form)
1015 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
1016 * - placeholder - set to NULL to disable
1017 * - multiple - bool
1018 * - context - @see CRM_Core_DAO::buildOptionsContext
1019 * @param bool $required
1020 * @throws CRM_Core_Exception
1021 * @return HTML_QuickForm_Element
1022 */
1023 public function addSelect($name, $props = array(), $required = FALSE) {
1024 if (!isset($props['entity'])) {
1025 $props['entity'] = CRM_Utils_Api::getEntityName($this);
1026 }
1027 if (!isset($props['field'])) {
1028 $props['field'] = strrpos($name, '[') ? rtrim(substr($name, 1 + strrpos($name, '[')), ']') : $name;
1029 }
1030 // Fetch options from the api unless passed explicitly
1031 if (isset($props['options'])) {
1032 $options = $props['options'];
1033 }
1034 else {
1035 $info = civicrm_api3($props['entity'], 'getoptions', $props);
1036 $options = $info['values'];
1037 }
1038 if (!array_key_exists('placeholder', $props)) {
1039 $props['placeholder'] = $required ? ts('- select -') : CRM_Utils_Array::value('context', $props) == 'search' ? ts('- any -') : ts('- none -');
1040 }
1041 // Handle custom field
1042 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1043 list(, $id) = explode('_', $name);
1044 $label = isset($props['label']) ? $props['label'] : CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
1045 $gid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
1046 if (CRM_Utils_Array::value('context', $props) != 'search') {
1047 $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);
1048 }
1049 }
1050 // Core field
1051 else {
1052 $info = civicrm_api3($props['entity'], 'getfields');
1053 foreach($info['values'] as $uniqueName => $fieldSpec) {
1054 if (
1055 $uniqueName === $props['field'] ||
1056 CRM_Utils_Array::value('name', $fieldSpec) === $props['field'] ||
1057 in_array($props['field'], CRM_Utils_Array::value('api.aliases', $fieldSpec, array()))
1058 ) {
1059 break;
1060 }
1061 }
1062 $label = isset($props['label']) ? $props['label'] : $fieldSpec['title'];
1063 if (CRM_Utils_Array::value('context', $props) != 'search') {
1064 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec);
1065 }
1066 }
1067 $props['class'] = (isset($props['class']) ? $props['class'] . ' ' : '') . "crm-select2";
1068 $props['data-api-entity'] = $props['entity'];
1069 $props['data-api-field'] = $props['field'];
1070 CRM_Utils_Array::remove($props, 'label', 'entity', 'field', 'option_url', 'options', 'context');
1071 return $this->add('select', $name, $label, $options, $required, $props);
1072 }
1073
1074 /**
1075 * Add a widget for selecting/editing/creating/copying a profile form
1076 *
1077 * @param string $name
1078 * HTML form-element name.
1079 * @param string $label
1080 * Printable label.
1081 * @param string $allowCoreTypes
1082 * Only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'.
1083 * @param string $allowSubTypes
1084 * Only present a UFGroup if its group_type is compatible with $allowSubypes.
1085 * @param array $entities
1086 * @param bool $default
1087 * //CRM-15427.
1088 */
1089 public function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities, $default = FALSE) {
1090 // Output widget
1091 // FIXME: Instead of adhoc serialization, use a single json_encode()
1092 CRM_UF_Page_ProfileEditor::registerProfileScripts();
1093 CRM_UF_Page_ProfileEditor::registerSchemas(CRM_Utils_Array::collect('entity_type', $entities));
1094 $this->add('text', $name, $label, array(
1095 'class' => 'crm-profile-selector',
1096 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1097 'data-group-type' => CRM_Core_BAO_UFGroup::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1098 'data-entities' => json_encode($entities),
1099 //CRM-15427
1100 'data-default' => $default,
1101 ));
1102 }
1103
1104 /**
1105 * @param string $name
1106 * @param $label
1107 * @param $attributes
1108 * @param bool $forceTextarea
1109 */
1110 public function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1111 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1112 // 2. Based on the option, initialise proper editor
1113 $editorID = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
1114 'editor_id'
1115 );
1116 $editor = strtolower(CRM_Utils_Array::value($editorID,
1117 CRM_Core_OptionGroup::values('wysiwyg_editor')
1118 ));
1119 if (!$editor || $forceTextarea) {
1120 $editor = 'textarea';
1121 }
1122 if ($editor == 'joomla default editor') {
1123 $editor = 'joomlaeditor';
1124 }
1125
1126 if ($editor == 'drupal default editor') {
1127 $editor = 'drupalwysiwyg';
1128 }
1129
1130 //lets add the editor as a attribute
1131 $attributes['editor'] = $editor;
1132
1133 $this->addElement($editor, $name, $label, $attributes);
1134 $this->assign('editor', $editor);
1135
1136 // include wysiwyg editor js files
1137 // FIXME: This code does not make any sense
1138 $includeWysiwygEditor = FALSE;
1139 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1140 if (!$includeWysiwygEditor) {
1141 $includeWysiwygEditor = TRUE;
1142 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1143 }
1144
1145 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1146 }
1147
1148 /**
1149 * @param int $id
1150 * @param $title
1151 * @param null $required
1152 * @param null $extra
1153 */
1154 public function addCountry($id, $title, $required = NULL, $extra = NULL) {
1155 $this->addElement('select', $id, $title,
1156 array(
1157 '' => ts('- select -')) + CRM_Core_PseudoConstant::country(), $extra
1158 );
1159 if ($required) {
1160 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1161 }
1162 }
1163
1164 /**
1165 * @param string $name
1166 * @param $label
1167 * @param $options
1168 * @param $attributes
1169 * @param null $required
1170 * @param null $javascriptMethod
1171 */
1172 public function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1173
1174 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1175
1176 if ($required) {
1177 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1178 }
1179 }
1180
1181 /**
1182 * @return null
1183 */
1184 public function getRootTitle() {
1185 return NULL;
1186 }
1187
1188 /**
1189 * @return string
1190 */
1191 public function getCompleteTitle() {
1192 return $this->getRootTitle() . $this->getTitle();
1193 }
1194
1195 /**
1196 * @return CRM_Core_Smarty
1197 */
1198 public static function &getTemplate() {
1199 return self::$_template;
1200 }
1201
1202 /**
1203 * @param $elementName
1204 */
1205 public function addUploadElement($elementName) {
1206 $uploadNames = $this->get('uploadNames');
1207 if (!$uploadNames) {
1208 $uploadNames = array();
1209 }
1210 if (is_array($elementName)) {
1211 foreach ($elementName as $name) {
1212 if (!in_array($name, $uploadNames)) {
1213 $uploadNames[] = $name;
1214 }
1215 }
1216 }
1217 else {
1218 if (!in_array($elementName, $uploadNames)) {
1219 $uploadNames[] = $elementName;
1220 }
1221 }
1222 $this->set('uploadNames', $uploadNames);
1223
1224 $config = CRM_Core_Config::singleton();
1225 if (!empty($uploadNames)) {
1226 $this->controller->addUploadAction($config->customFileUploadDir, $uploadNames);
1227 }
1228 }
1229
1230 /**
1231 * @return string
1232 */
1233 public function buttonType() {
1234 $uploadNames = $this->get('uploadNames');
1235 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ? 'upload' : 'next';
1236 $this->assign('buttonType', $buttonType);
1237 return $buttonType;
1238 }
1239
1240 /**
1241 * @param $name
1242 *
1243 * @return null
1244 */
1245 public function getVar($name) {
1246 return isset($this->$name) ? $this->$name : NULL;
1247 }
1248
1249 /**
1250 * @param $name
1251 * @param $value
1252 */
1253 public function setVar($name, $value) {
1254 $this->$name = $value;
1255 }
1256
1257 /**
1258 * Add date
1259 * @param string $name
1260 * Name of the element.
1261 * @param string $label
1262 * Label of the element.
1263 * @param array $attributes
1264 * Key / value pair.
1265 *
1266 * // if you need time
1267 * $attributes = array(
1268 * 'addTime' => true,
1269 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1270 * );
1271 * @param bool $required
1272 * True if required.
1273 *
1274 */
1275 public function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1276 if (!empty($attributes['formatType'])) {
1277 // get actual format
1278 $params = array('name' => $attributes['formatType']);
1279 $values = array();
1280
1281 // cache date information
1282 static $dateFormat;
1283 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1284 if (empty($dateFormat[$key])) {
1285 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1286 $dateFormat[$key] = $values;
1287 }
1288 else {
1289 $values = $dateFormat[$key];
1290 }
1291
1292 if ($values['date_format']) {
1293 $attributes['format'] = $values['date_format'];
1294 }
1295
1296 if (!empty($values['time_format'])) {
1297 $attributes['timeFormat'] = $values['time_format'];
1298 }
1299 $attributes['startOffset'] = $values['start'];
1300 $attributes['endOffset'] = $values['end'];
1301 }
1302
1303 $config = CRM_Core_Config::singleton();
1304 if (empty($attributes['format'])) {
1305 $attributes['format'] = $config->dateInputFormat;
1306 }
1307
1308 if (!isset($attributes['startOffset'])) {
1309 $attributes['startOffset'] = 10;
1310 }
1311
1312 if (!isset($attributes['endOffset'])) {
1313 $attributes['endOffset'] = 10;
1314 }
1315
1316 $this->add('text', $name, $label, $attributes);
1317
1318 if (!empty($attributes['addTime']) || !empty($attributes['timeFormat'])) {
1319
1320 if (!isset($attributes['timeFormat'])) {
1321 $timeFormat = $config->timeInputFormat;
1322 }
1323 else {
1324 $timeFormat = $attributes['timeFormat'];
1325 }
1326
1327 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1328 if ($timeFormat) {
1329 $show24Hours = TRUE;
1330 if ($timeFormat == 1) {
1331 $show24Hours = FALSE;
1332 }
1333
1334 //CRM-6664 -we are having time element name
1335 //in either flat string or an array format.
1336 $elementName = $name . '_time';
1337 if (substr($name, -1) == ']') {
1338 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1339 }
1340
1341 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1342 }
1343 }
1344
1345 if ($required) {
1346 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1347 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1348 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1349 }
1350 }
1351 }
1352
1353 /**
1354 * Function that will add date and time
1355 */
1356 public function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1357 $addTime = array('addTime' => TRUE);
1358 if (is_array($attributes)) {
1359 $attributes = array_merge($attributes, $addTime);
1360 }
1361 else {
1362 $attributes = $addTime;
1363 }
1364
1365 $this->addDate($name, $label, $required, $attributes);
1366 }
1367
1368 /**
1369 * Add a currency and money element to the form
1370 */
1371 function addMoney(
1372 $name,
1373 $label,
1374 $required = FALSE,
1375 $attributes = NULL,
1376 $addCurrency = TRUE,
1377 $currencyName = 'currency',
1378 $defaultCurrency = NULL,
1379 $freezeCurrency = FALSE
1380 ) {
1381 $element = $this->add('text', $name, $label, $attributes, $required);
1382 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1383
1384 if ($addCurrency) {
1385 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1386 }
1387
1388 return $element;
1389 }
1390
1391 /**
1392 * Add currency element to the form
1393 */
1394 function addCurrency(
1395 $name = 'currency',
1396 $label = NULL,
1397 $required = TRUE,
1398 $defaultCurrency = NULL,
1399 $freezeCurrency = FALSE
1400 ) {
1401 $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
1402 $options = array('class' => 'crm-select2 eight');
1403 if (!$required) {
1404 $currencies = array('' => '') + $currencies;
1405 $options['placeholder'] = ts('- none -');
1406 }
1407 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1408 if ($freezeCurrency) {
1409 $ele->freeze();
1410 }
1411 if (!$defaultCurrency) {
1412 $config = CRM_Core_Config::singleton();
1413 $defaultCurrency = $config->defaultCurrency;
1414 }
1415 $this->setDefaults(array($name => $defaultCurrency));
1416 }
1417
1418 /**
1419 * Create a single or multiple entity ref field
1420 * @param string $name
1421 * @param string $label
1422 * @param array $props
1423 * Mix of html and widget properties, including:.
1424 * - select - params to give to select2 widget
1425 * - entity - defaults to contact
1426 * - create - can the user create a new entity on-the-fly?
1427 * Set to TRUE if entity is contact and you want the default profiles,
1428 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1429 * note that permissions are checked automatically
1430 * - api - array of settings for the getlist api wrapper
1431 * note that it accepts a 'params' setting which will be passed to the underlying api
1432 * - placeholder - string
1433 * - multiple - bool
1434 * - class, etc. - other html properties
1435 * @param bool $required
1436 *
1437 * @return HTML_QuickForm_Element
1438 */
1439 public function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1440 require_once "api/api.php";
1441 $config = CRM_Core_Config::singleton();
1442 // Default properties
1443 $props['api'] = CRM_Utils_Array::value('api', $props, array());
1444 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array::value('entity', $props, 'contact'));
1445 $props['class'] = ltrim(CRM_Utils_Array::value('class', $props, '') . ' crm-form-entityref');
1446
1447 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission::check('edit all contacts') || CRM_Core_Permission::check('add contacts'))) {
1448 unset($props['create']);
1449 }
1450
1451 $props['placeholder'] = CRM_Utils_Array::value('placeholder', $props, $required ? ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1452
1453 $defaults = array();
1454 if (!empty($props['multiple'])) {
1455 $defaults['multiple'] = TRUE;
1456 }
1457 $props['select'] = CRM_Utils_Array::value('select', $props, array()) + $defaults;
1458
1459 $this->formatReferenceFieldAttributes($props);
1460 return $this->add('text', $name, $label, $props, $required);
1461 }
1462
1463 /**
1464 * @param $props
1465 */
1466 private function formatReferenceFieldAttributes(&$props) {
1467 $props['data-select-params'] = json_encode($props['select']);
1468 $props['data-api-params'] = $props['api'] ? json_encode($props['api']) : NULL;
1469 $props['data-api-entity'] = $props['entity'];
1470 if (!empty($props['create'])) {
1471 $props['data-create-links'] = json_encode($props['create']);
1472 }
1473 CRM_Utils_Array::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1474 }
1475
1476 /**
1477 * Convert all date fields within the params to mysql date ready for the
1478 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1479 * and if time is defined it is incorporated
1480 *
1481 * @param array $params
1482 * Input params from the form.
1483 *
1484 * @todo it would probably be better to work on $this->_params than a passed array
1485 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1486 * handling from BAO
1487 */
1488 public function convertDateFieldsToMySQL(&$params){
1489 foreach ($this->_dateFields as $fieldName => $specs){
1490 if(!empty($params[$fieldName])){
1491 $params[$fieldName] = CRM_Utils_Date::isoToMysql(
1492 CRM_Utils_Date::processDate(
1493 $params[$fieldName],
1494 CRM_Utils_Array::value("{$fieldName}_time", $params), TRUE)
1495 );
1496 }
1497 else{
1498 if(isset($specs['default'])){
1499 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1500 }
1501 }
1502 }
1503 }
1504
1505 /**
1506 * @param $elementName
1507 */
1508 public function removeFileRequiredRules($elementName) {
1509 $this->_required = array_diff($this->_required, array($elementName));
1510 if (isset($this->_rules[$elementName])) {
1511 foreach ($this->_rules[$elementName] as $index => $ruleInfo) {
1512 if ($ruleInfo['type'] == 'uploadedfile') {
1513 unset($this->_rules[$elementName][$index]);
1514 }
1515 }
1516 if (empty($this->_rules[$elementName])) {
1517 unset($this->_rules[$elementName]);
1518 }
1519 }
1520 }
1521
1522 /**
1523 * Function that can be defined in Form to override or
1524 * perform specific action on cancel action
1525 *
1526 */
1527 public function cancelAction() {
1528 }
1529
1530 /**
1531 * Helper function to verify that required fields have been filled
1532 * Typically called within the scope of a FormRule function
1533 */
1534 public static function validateMandatoryFields($fields, $values, &$errors) {
1535 foreach ($fields as $name => $fld) {
1536 if (!empty($fld['is_required']) && CRM_Utils_System::isNull(CRM_Utils_Array::value($name, $values))) {
1537 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1538 }
1539 }
1540 }
1541
1542 /**
1543 * Get contact if for a form object. Prioritise
1544 * - cid in URL if 0 (on behalf on someoneelse)
1545 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1546 * - logged in user id if it matches the one in the cid in the URL
1547 * - contact id validated from a checksum from a checksum
1548 * - cid from the url if the caller has ACL permission to view
1549 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1550 *
1551 * @return mixed NULL|integer
1552 */
1553 public function getContactID() {
1554 $tempID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
1555 if(isset($this->_params) && isset($this->_params['select_contact_id'])) {
1556 $tempID = $this->_params['select_contact_id'];
1557 }
1558 if(isset($this->_params, $this->_params[0]) && !empty($this->_params[0]['select_contact_id'])) {
1559 // event form stores as an indexed array, contribution form not so much...
1560 $tempID = $this->_params[0]['select_contact_id'];
1561 }
1562
1563 // force to ignore the authenticated user
1564 if ($tempID === '0' || $tempID === 0) {
1565 // we set the cid on the form so that this will be retained for the Confirm page
1566 // in the multi-page form & prevent us returning the $userID when this is called
1567 // from that page
1568 // we don't really need to set it when $tempID is set because the params have that stored
1569 $this->set('cid', 0);
1570 return $tempID;
1571 }
1572
1573 $userID = $this->getLoggedInUserContactID();
1574
1575 if ($tempID == $userID) {
1576 return $userID;
1577 }
1578
1579 //check if this is a checksum authentication
1580 $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this);
1581 if ($userChecksum) {
1582 //check for anonymous user.
1583 $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($tempID, $userChecksum);
1584 if ($validUser) {
1585 return $tempID;
1586 }
1587 }
1588 // check if user has permission, CRM-12062
1589 else if ($tempID && CRM_Contact_BAO_Contact_Permission::allow($tempID)) {
1590 return $tempID;
1591 }
1592
1593 return $userID;
1594 }
1595
1596 /**
1597 * Get the contact id of the logged in user
1598 */
1599 public function getLoggedInUserContactID() {
1600 // check if the user is logged in and has a contact ID
1601 $session = CRM_Core_Session::singleton();
1602 return $session->get('userID');
1603 }
1604
1605 /**
1606 * Add autoselector field -if user has permission to view contacts
1607 * If adding this to a form you also need to add to the tpl e.g
1608 *
1609 * {if !empty($selectable)}
1610 * <div class="crm-summary-row">
1611 * <div class="crm-label">{$form.select_contact.label}</div>
1612 * <div class="crm-content">
1613 * {$form.select_contact.html}
1614 * </div>
1615 * </div>
1616 * {/if}
1617 *
1618 * @param array $profiles
1619 * Ids of profiles that are on the form (to be autofilled).
1620 * @param array $autoCompleteField
1621 *
1622 * - name_field
1623 * - id_field
1624 * - url (for ajax lookup)
1625 *
1626 * @todo add data attributes so we can deal with multiple instances on a form
1627 */
1628 public function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1629 $autoCompleteField = array_merge(array(
1630 'id_field' => 'select_contact_id',
1631 'placeholder' => ts('Select someone else ...'),
1632 'show_hide' => TRUE,
1633 'api' => array('params' => array('contact_type' => 'Individual'))
1634 ), $autoCompleteField);
1635
1636 if($this->canUseAjaxContactLookups()) {
1637 $this->assign('selectable', $autoCompleteField['id_field']);
1638 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1639
1640 CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js', 1, 'html-header')
1641 ->addSetting(array(
1642 'form' => array('autocompletes' => $autoCompleteField),
1643 'ids' => array('profile' => $profiles),
1644 ));
1645 }
1646 }
1647
1648 /**
1649 *
1650 */
1651 public function canUseAjaxContactLookups() {
1652 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1653 CRM_Core_Permission::check(array(array('access AJAX API', 'access CiviCRM')))) {
1654 return TRUE;
1655 }
1656 }
1657
1658 /**
1659 * Add the options appropriate to cid = zero - ie. autocomplete
1660 *
1661 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1662 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1663 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1664 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1665 */
1666 public function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1667 $this->assign('nocid', TRUE);
1668 $profiles = array();
1669 if($this->_values['custom_pre_id']) {
1670 $profiles[] = $this->_values['custom_pre_id'];
1671 }
1672 if($this->_values['custom_post_id']) {
1673 $profiles = array_merge($profiles, (array) $this->_values['custom_post_id']);
1674 }
1675 if($onlinePaymentProcessorEnabled) {
1676 $profiles[] = 'billing';
1677 }
1678 if(!empty($this->_values)) {
1679 $this->addAutoSelector($profiles);
1680 }
1681 }
1682
1683 /**
1684 * Set default values on form for given contact (or no contact defaults)
1685 *
1686 * @param mixed $profile_id
1687 * (can be id, or profile name).
1688 * @param int $contactID
1689 *
1690 * @return array
1691 */
1692 public function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1693 try{
1694 $defaults = civicrm_api3('profile', 'getsingle', array(
1695 'profile_id' => (array) $profile_id,
1696 'contact_id' => $contactID,
1697 ));
1698 return $defaults;
1699 }
1700 catch (Exception $e) {
1701 // the try catch block gives us silent failure -not 100% sure this is a good idea
1702 // as silent failures are often worse than noisy ones
1703 return array();
1704 }
1705 }
1706
1707 /**
1708 * Sets form attribute
1709 * @see CRM.loadForm
1710 */
1711 public function preventAjaxSubmit() {
1712 $this->setAttribute('data-no-ajax-submit', 'true');
1713 }
1714
1715 /**
1716 * Sets form attribute
1717 * @see CRM.loadForm
1718 */
1719 public function allowAjaxSubmit() {
1720 $this->removeAttribute('data-no-ajax-submit');
1721 }
1722
1723 /**
1724 * Sets page title based on entity and action
1725 * @param string $entityLabel
1726 */
1727 public function setPageTitle($entityLabel) {
1728 switch ($this->_action) {
1729 case CRM_Core_Action::ADD:
1730 CRM_Utils_System::setTitle(ts('New %1', array(1 => $entityLabel)));
1731 break;
1732
1733 case CRM_Core_Action::UPDATE:
1734 CRM_Utils_System::setTitle(ts('Edit %1', array(1 => $entityLabel)));
1735 break;
1736
1737 case CRM_Core_Action::VIEW:
1738 case CRM_Core_Action::PREVIEW:
1739 CRM_Utils_System::setTitle(ts('View %1', array(1 => $entityLabel)));
1740 break;
1741
1742 case CRM_Core_Action::DELETE:
1743 CRM_Utils_System::setTitle(ts('Delete %1', array(1 => $entityLabel)));
1744 break;
1745 }
1746 }
1747
1748 /**
1749 * Create a chain-select target field. All settings are optional; the defaults usually work.
1750 *
1751 * @param string $elementName
1752 * @param array $settings
1753 *
1754 * @return HTML_QuickForm_Element
1755 */
1756 public function addChainSelect($elementName, $settings = array()) {
1757 $props = $settings += array(
1758 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array('country', 'Country', 'state_province', 'StateProvince'), $elementName),
1759 'data-callback' => strpos($elementName, 'rovince') ? 'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
1760 'label' => strpos($elementName, 'rovince') ? ts('State/Province') : ts('County'),
1761 'data-empty-prompt' => strpos($elementName, 'rovince') ? ts('Choose country first') : ts('Choose state first'),
1762 'data-none-prompt' => ts('- N/A -'),
1763 'multiple' => FALSE,
1764 'required' => FALSE,
1765 'placeholder' => empty($settings['required']) ? ts('- none -') : ts('- select -'),
1766 );
1767 CRM_Utils_Array::remove($props, 'label', 'required', 'control_field');
1768 $props['class'] = (empty($props['class']) ? '' : "{$props['class']} ") . 'crm-select2';
1769 $props['data-select-prompt'] = $props['placeholder'];
1770 $props['data-name'] = $elementName;
1771
1772 $this->_chainSelectFields[$settings['control_field']] = $elementName;
1773
1774 // Passing NULL instead of an array of options
1775 // CRM-15225 - normally QF will reject any selected values that are not part of the field's options, but due to a
1776 // quirk in our patched version of HTML_QuickForm_select, this doesn't happen if the options are NULL
1777 // which seems a bit dirty but it allows our dynamically-popuplated select element to function as expected.
1778 return $this->add('select', $elementName, $settings['label'], NULL, $settings['required'], $props);
1779 }
1780
1781 /**
1782 * Set options and attributes for chain select fields based on the controlling field's value
1783 */
1784 private function preProcessChainSelectFields() {
1785 foreach ($this->_chainSelectFields as $control => $target) {
1786 $targetField = $this->getElement($target);
1787 $targetType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'county' : 'stateProvince';
1788 $options = array();
1789 // If the control field is on the form, setup chain-select and dynamically populate options
1790 if ($this->elementExists($control)) {
1791 $controlField = $this->getElement($control);
1792 $controlType = $targetType == 'county' ? 'stateProvince' : 'country';
1793
1794 $targetField->setAttribute('class', $targetField->getAttribute('class') . ' crm-chain-select-target');
1795
1796 $css = (string) $controlField->getAttribute('class');
1797 $controlField->updateAttributes(array(
1798 'class' => ($css ? "$css " : 'crm-select2 ') . 'crm-chain-select-control',
1799 'data-target' => $target,
1800 ));
1801 $controlValue = $controlField->getValue();
1802 if ($controlValue) {
1803 $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
1804 if (!$options) {
1805 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
1806 }
1807 } else {
1808 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
1809 $targetField->setAttribute('disabled', 'disabled');
1810 }
1811 }
1812 // Control field not present - fall back to loading default options
1813 else {
1814 $options = CRM_Core_PseudoConstant::$targetType();
1815 }
1816 if (!$targetField->getAttribute('multiple')) {
1817 $options = array('' => $targetField->getAttribute('placeholder')) + $options;
1818 $targetField->removeAttribute('placeholder');
1819 }
1820 $targetField->_options = array();
1821 $targetField->loadArray($options);
1822 }
1823 }
1824
1825 /**
1826 * Validate country / state / county match and suppress unwanted "required" errors
1827 */
1828 private function validateChainSelectFields() {
1829 foreach ($this->_chainSelectFields as $control => $target) {
1830 if ($this->elementExists($control)) {
1831 $controlValue = (array) $this->getElementValue($control);
1832 $targetField = $this->getElement($target);
1833 $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'stateProvince' : 'country';
1834 $targetValue = array_filter((array) $targetField->getValue());
1835 if ($targetValue || $this->getElementError($target)) {
1836 $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
1837 if ($targetValue) {
1838 if (!array_intersect($targetValue, array_keys($options))) {
1839 $this->setElementError($target, $controlType == 'country' ? ts('State/Province does not match the selected Country') : ts('County does not match the selected State/Province'));
1840 }
1841 } // Suppress "required" error for field if it has no options
1842 elseif (!$options) {
1843 $this->setElementError($target, NULL);
1844 }
1845 }
1846 }
1847 }
1848 }
1849 }