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