CRM-21849: Cast contact_id_a before comparison
[civicrm-core.git] / CRM / Core / Form.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
8c9251b3 6 | Copyright CiviCRM LLC (c) 2004-2018 |
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
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
8c9251b3 34 * @copyright CiviCRM LLC (c) 2004-2018
6a488035
TO
35 */
36
37require_once 'HTML/QuickForm/Page.php';
28518c90
EM
38
39/**
40 * Class CRM_Core_Form
41 */
6a488035
TO
42class CRM_Core_Form extends HTML_QuickForm_Page {
43
44 /**
45 * The state object that this form belongs to
46 * @var object
47 */
48 protected $_state;
49
50 /**
51 * The name of this form
52 * @var string
53 */
54 protected $_name;
55
56 /**
57 * The title of this form
58 * @var string
59 */
60 protected $_title = NULL;
61
73afe1e6 62 /**
63 * The default values for the form.
64 *
65 * @var array
66 */
67 public $_defaults = array();
68
6a488035 69 /**
2eee184e
TO
70 * (QUASI-PROTECTED) The options passed into this form
71 *
72 * This field should marked `protected` and is not generally
73 * intended for external callers, but some edge-cases do use it.
74 *
6a488035
TO
75 * @var mixed
76 */
2eee184e 77 public $_options = NULL;
6a488035
TO
78
79 /**
2eee184e
TO
80 * (QUASI-PROTECTED) The mode of operation for this form
81 *
82 * This field should marked `protected` and is not generally
83 * intended for external callers, but some edge-cases do use it.
84 *
6a488035
TO
85 * @var int
86 */
2eee184e 87 public $_action;
6a488035 88
1b9f9ca3
EM
89 /**
90 * Available payment processors.
91 *
92 * As part of trying to consolidate various payment pages we store processors here & have functions
93 * at this level to manage them.
94 *
95 * @var array
96 * An array of payment processor details with objects loaded in the 'object' field.
97 */
42e3a033 98 protected $_paymentProcessors;
1b9f9ca3
EM
99
100 /**
101 * Available payment processors (IDS).
102 *
103 * As part of trying to consolidate various payment pages we store processors here & have functions
cbcb5b49 104 * at this level to manage them. An alternative would be to have a separate Form that is inherited
105 * by all forms that allow payment processing.
1b9f9ca3
EM
106 *
107 * @var array
108 * An array of the IDS available on this form.
109 */
110 public $_paymentProcessorIDs;
111
cbcb5b49 112 /**
113 * Default or selected processor id.
114 *
115 * As part of trying to consolidate various payment pages we store processors here & have functions
116 * at this level to manage them. An alternative would be to have a separate Form that is inherited
117 * by all forms that allow payment processing.
118 *
119 * @var int
120 */
121 protected $_paymentProcessorID;
122
123 /**
124 * Is pay later enabled for the form.
125 *
126 * As part of trying to consolidate various payment pages we store processors here & have functions
127 * at this level to manage them. An alternative would be to have a separate Form that is inherited
128 * by all forms that allow payment processing.
129 *
130 * @var int
131 */
132 protected $_is_pay_later_enabled;
133
6a488035 134 /**
100fef9d 135 * The renderer used for this form
6a488035
TO
136 *
137 * @var object
138 */
139 protected $_renderer;
140
5d86176b 141 /**
142 * An array to hold a list of datefields on the form
143 * so that they can be converted to ISO in a consistent manner
144 *
145 * @var array
146 *
147 * e.g on a form declare $_dateFields = array(
148 * 'receive_date' => array('default' => 'now'),
149 * );
150 * then in postProcess call $this->convertDateFieldsToMySQL($formValues)
151 * to have the time field re-incorporated into the field & 'now' set if
152 * no value has been passed in
153 */
154 protected $_dateFields = array();
155
6a488035 156 /**
100fef9d 157 * Cache the smarty template for efficiency reasons
6a488035
TO
158 *
159 * @var CRM_Core_Smarty
160 */
161 static protected $_template;
162
461fa5fb 163 /**
164 * Indicate if this form should warn users of unsaved changes
165 */
166 protected $unsavedChangesWarn;
167
03a7ec8f 168 /**
fc05b8da 169 * What to return to the client if in ajax mode (snippet=json)
03a7ec8f
CW
170 *
171 * @var array
172 */
173 public $ajaxResponse = array();
174
118e964e
CW
175 /**
176 * Url path used to reach this page
177 *
178 * @var array
179 */
180 public $urlPath = array();
181
d77a0a58
EM
182 /**
183 * @var CRM_Core_Controller
184 */
185 public $controller;
4a44fd8a 186
6a488035 187 /**
100fef9d 188 * Constants for attributes for various form elements
6a488035
TO
189 * attempt to standardize on the number of variations that we
190 * use of the below form elements
191 *
192 * @var const string
193 */
7da04cde 194 const ATTR_SPACING = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
6a488035
TO
195
196 /**
197 * All checkboxes are defined with a common prefix. This allows us to
198 * have the same javascript to check / clear all the checkboxes etc
199 * If u have multiple groups of checkboxes, you will need to give them different
200 * ids to avoid potential name collision
201 *
fffe9ee1 202 * @var string|int
6a488035 203 */
7da04cde 204 const CB_PREFIX = 'mark_x_', CB_PREFIY = 'mark_y_', CB_PREFIZ = 'mark_z_', CB_PREFIX_LEN = 7;
6a488035 205
1d07e7ab
CW
206 /**
207 * @internal to keep track of chain-select fields
208 * @var array
209 */
210 private $_chainSelectFields = array();
211
d8f1758d
CW
212 /**
213 * Extra input types we support via the "add" method
214 * @var array
215 */
216 public static $html5Types = array(
217 'number',
218 'url',
219 'email',
1192bd09 220 'color',
d8f1758d
CW
221 );
222
6a488035 223 /**
4b62bc4f 224 * Constructor for the basic form page.
6a488035
TO
225 *
226 * We should not use QuickForm directly. This class provides a lot
227 * of default convenient functions, rules and buttons
228 *
6a0b768e
TO
229 * @param object $state
230 * State associated with this form.
fffe9ee1 231 * @param \const|\enum|int $action The mode the form is operating in (None/Create/View/Update/Delete)
6a0b768e
TO
232 * @param string $method
233 * The type of http method used (GET/POST).
234 * @param string $name
235 * The name of the form if different from class name.
6a488035 236 *
dd244018 237 * @return \CRM_Core_Form
6a488035 238 */
2da40d21 239 public function __construct(
6a488035
TO
240 $state = NULL,
241 $action = CRM_Core_Action::NONE,
242 $method = 'post',
f9f40af3 243 $name = NULL
6a488035
TO
244 ) {
245
246 if ($name) {
247 $this->_name = $name;
248 }
249 else {
b50fdacc 250 // CRM-15153 - FIXME this name translates to a DOM id and is not always unique!
6a488035
TO
251 $this->_name = CRM_Utils_String::getClassName(CRM_Utils_System::getClassName($this));
252 }
253
6ef04c72 254 parent::__construct($this->_name, $method);
6a488035
TO
255
256 $this->_state =& $state;
257 if ($this->_state) {
258 $this->_state->setName($this->_name);
259 }
260 $this->_action = (int) $action;
261
262 $this->registerRules();
263
264 // let the constructor initialize this, should happen only once
265 if (!isset(self::$_template)) {
266 self::$_template = CRM_Core_Smarty::singleton();
267 }
b50fdacc
CW
268 // Workaround for CRM-15153 - give each form a reasonably unique css class
269 $this->addClass(CRM_Utils_System::getClassName($this));
03a7ec8f 270
819d4cbb 271 $this->assign('snippet', CRM_Utils_Array::value('snippet', $_GET));
6a488035
TO
272 }
273
023e90c3 274 /**
e51d62d3 275 * Add one or more css classes to the form.
276 *
100fef9d 277 * @param string $className
023e90c3
CW
278 */
279 public function addClass($className) {
280 $classes = $this->getAttribute('class');
281 $this->setAttribute('class', ($classes ? "$classes " : '') . $className);
282 }
283
6a488035 284 /**
fe482240 285 * Register all the standard rules that most forms potentially use.
6a488035 286 */
00be9182 287 public function registerRules() {
6a488035 288 static $rules = array(
353ffa53
TO
289 'title',
290 'longTitle',
291 'variable',
292 'qfVariable',
293 'phone',
294 'integer',
295 'query',
296 'url',
297 'wikiURL',
298 'domain',
299 'numberOfDigit',
300 'date',
301 'currentDate',
302 'asciiFile',
303 'htmlFile',
304 'utf8File',
305 'objectExists',
306 'optionExists',
307 'postalCode',
308 'money',
309 'positiveInteger',
310 'xssString',
311 'fileExists',
d9d7e7dd 312 'settingPath',
353ffa53
TO
313 'autocomplete',
314 'validContact',
6a488035
TO
315 );
316
317 foreach ($rules as $rule) {
318 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
319 }
320 }
321
322 /**
e51d62d3 323 * Simple easy to use wrapper around addElement.
324 *
325 * Deal with simple validation rules.
6a488035 326 *
bae056c6
CW
327 * @param string $type
328 * @param string $name
77b97be7 329 * @param string $label
bae056c6 330 * @param string|array $attributes (options for select elements)
77b97be7 331 * @param bool $required
6a0b768e
TO
332 * @param array $extra
333 * (attributes for select elements).
d86e674f 334 * For datepicker elements this is consistent with the data
335 * from CRM_Utils_Date::getDatePickerExtra
6a488035 336 *
e51d62d3 337 * @return HTML_QuickForm_Element
338 * Could be an error object
6a488035 339 */
2da40d21 340 public function &add(
f9f40af3 341 $type, $name, $label = '',
908fe4e6 342 $attributes = '', $required = FALSE, $extra = NULL
6a488035 343 ) {
092cb9c5 344 // Fudge some extra types that quickform doesn't support
1192bd09 345 $inputType = $type;
d8f1758d 346 if ($type == 'wysiwyg' || in_array($type, self::$html5Types)) {
66659cd7 347 $attributes = ($attributes ? $attributes : array()) + array('class' => '');
092cb9c5 348 $attributes['class'] = ltrim($attributes['class'] . " crm-form-$type");
7ad5ae6a
CW
349 if ($type == 'wysiwyg' && isset($attributes['preset'])) {
350 $attributes['data-preset'] = $attributes['preset'];
351 unset($attributes['preset']);
352 }
092cb9c5 353 $type = $type == 'wysiwyg' ? 'textarea' : 'text';
b608cfb1 354 }
b733747a
CW
355 // Like select but accepts rich array data (with nesting, colors, icons, etc) as option list.
356 if ($inputType == 'select2') {
357 $type = 'text';
358 $options = $attributes;
359 $attributes = $attributes = ($extra ? $extra : array()) + array('class' => '');
360 $attributes['class'] = ltrim($attributes['class'] . " crm-select2 crm-form-select2");
361 $attributes['data-select-params'] = json_encode(array('data' => $options, 'multiple' => !empty($attributes['multiple'])));
362 unset($attributes['multiple']);
363 $extra = NULL;
364 }
a2973721 365 // @see http://wiki.civicrm.org/confluence/display/CRMDOC/crmDatepicker
238fee7f 366 if ($type == 'datepicker') {
a2973721 367 $attributes = ($attributes ? $attributes : array());
238fee7f
CW
368 $attributes['data-crm-datepicker'] = json_encode((array) $extra);
369 $type = "text";
370 }
1d07e7ab 371 if ($type == 'select' && is_array($extra)) {
bae056c6 372 // Normalize this property
1d07e7ab
CW
373 if (!empty($extra['multiple'])) {
374 $extra['multiple'] = 'multiple';
375 }
376 else {
377 unset($extra['multiple']);
378 }
65e8615b 379 unset($extra['size'], $extra['maxlength']);
bae056c6
CW
380 // Add placeholder option for select
381 if (isset($extra['placeholder'])) {
382 if ($extra['placeholder'] === TRUE) {
383 $extra['placeholder'] = $required ? ts('- select -') : ts('- none -');
384 }
385 if (($extra['placeholder'] || $extra['placeholder'] === '') && empty($extra['multiple']) && is_array($attributes) && !isset($attributes[''])) {
386 $attributes = array('' => $extra['placeholder']) + $attributes;
387 }
388 }
908fe4e6
CW
389 }
390 $element = $this->addElement($type, $name, $label, $attributes, $extra);
6a488035
TO
391 if (HTML_QuickForm::isError($element)) {
392 CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
393 }
394
1192bd09
CW
395 if ($inputType == 'color') {
396 $this->addRule($name, ts('%1 must contain a color value e.g. #ffffff.', array(1 => $label)), 'regex', '/#[0-9a-fA-F]{6}/');
397 }
398
6a488035
TO
399 if ($required) {
400 if ($type == 'file') {
401 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
402 }
403 else {
404 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
405 }
406 if (HTML_QuickForm::isError($error)) {
407 CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
408 }
409 }
410
411 return $element;
412 }
413
414 /**
e51d62d3 415 * Preprocess form.
416 *
417 * This is called before buildForm. Any pre-processing that
418 * needs to be done for buildForm should be done here.
6a488035 419 *
8eedd10a 420 * This is a virtual function and should be redefined if needed.
6a488035 421 */
f9f40af3
TO
422 public function preProcess() {
423 }
6a488035
TO
424
425 /**
8eedd10a 426 * Called after the form is validated.
427 *
428 * Any processing of form state etc should be done in this function.
6a488035
TO
429 * Typically all processing associated with a form should be done
430 * here and relevant state should be stored in the session
431 *
432 * This is a virtual function and should be redefined if needed
6a488035 433 */
f9f40af3
TO
434 public function postProcess() {
435 }
6a488035
TO
436
437 /**
e51d62d3 438 * Main process wrapper.
439 *
440 * Implemented so that we can call all the hook functions.
441 *
6a0b768e
TO
442 * @param bool $allowAjax
443 * FIXME: This feels kind of hackish, ideally we would take the json-related code from this function.
7e9fdecf 444 * and bury it deeper down in the controller
6a488035 445 */
00be9182 446 public function mainProcess($allowAjax = TRUE) {
6a488035 447 $this->postProcess();
6a488035 448 $this->postProcessHook();
03a7ec8f 449
fc05b8da 450 // Respond with JSON if in AJAX context (also support legacy value '6')
353ffa53
TO
451 if ($allowAjax && !empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(
452 CRM_Core_Smarty::PRINT_JSON,
af9b09df 453 6,
353ffa53
TO
454 ))
455 ) {
03a7ec8f
CW
456 $this->ajaxResponse['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller->getButtonName());
457 $this->ajaxResponse['action'] = $this->_action;
18ddc127
CW
458 if (isset($this->_id) || isset($this->id)) {
459 $this->ajaxResponse['id'] = isset($this->id) ? $this->id : $this->_id;
460 }
03a7ec8f
CW
461 CRM_Core_Page_AJAX::returnJsonResponse($this->ajaxResponse);
462 }
6a488035
TO
463 }
464
465 /**
4b62bc4f 466 * The postProcess hook is typically called by the framework.
e51d62d3 467 *
6a488035
TO
468 * However in a few cases, the form exits or redirects early in which
469 * case it needs to call this function so other modules can do the needful
470 * Calling this function directly should be avoided if possible. In general a
471 * better way is to do setUserContext so the framework does the redirect
6a488035 472 */
00be9182 473 public function postProcessHook() {
6a488035
TO
474 CRM_Utils_Hook::postProcess(get_class($this), $this);
475 }
476
477 /**
8eedd10a 478 * This virtual function is used to build the form.
6a488035 479 *
8eedd10a 480 * It replaces the buildForm associated with QuickForm_Page. This allows us to put
481 * preProcess in front of the actual form building routine
6a488035 482 */
f9f40af3
TO
483 public function buildQuickForm() {
484 }
6a488035
TO
485
486 /**
8eedd10a 487 * This virtual function is used to set the default values of various form elements.
6a488035 488 *
a1a2a83d 489 * @return array|NULL
a6c01b45 490 * reference to the array of default values
6a488035 491 */
f9f40af3 492 public function setDefaultValues() {
a1a2a83d 493 return NULL;
f9f40af3 494 }
6a488035
TO
495
496 /**
8eedd10a 497 * This is a virtual function that adds group and global rules to the form.
6a488035 498 *
8eedd10a 499 * Keeping it distinct from the form to keep code small
500 * and localized in the form building code
6a488035 501 */
f9f40af3
TO
502 public function addRules() {
503 }
6a488035 504
b5c2afd0 505 /**
fe482240 506 * Performs the server side validation.
b5c2afd0 507 * @since 1.0
5c766a0b 508 * @return bool
a6c01b45 509 * true if no error found
b5c2afd0
EM
510 * @throws HTML_QuickForm_Error
511 */
00be9182 512 public function validate() {
6a488035
TO
513 $error = parent::validate();
514
bc999cd1
CW
515 $this->validateChainSelectFields();
516
6a9e89b4 517 $hookErrors = array();
6a488035
TO
518
519 CRM_Utils_Hook::validateForm(
520 get_class($this),
521 $this->_submitValues,
522 $this->_submitFiles,
523 $this,
524 $hookErrors
525 );
526
527 if (!empty($hookErrors)) {
528 $this->_errors += $hookErrors;
529 }
530
531 return (0 == count($this->_errors));
532 }
533
534 /**
3bdf1f3a 535 * Core function that builds the form.
536 *
537 * We redefine this function here and expect all CRM forms to build their form in the function
6a488035 538 * buildQuickForm.
6a488035 539 */
00be9182 540 public function buildForm() {
6a488035
TO
541 $this->_formBuilt = TRUE;
542
543 $this->preProcess();
544
21d2903d
AN
545 CRM_Utils_Hook::preProcess(get_class($this), $this);
546
6a488035
TO
547 $this->assign('translatePermission', CRM_Core_Permission::check('translate CiviCRM'));
548
549 if (
550 $this->controller->_key &&
551 $this->controller->_generateQFKey
552 ) {
553 $this->addElement('hidden', 'qfKey', $this->controller->_key);
554 $this->assign('qfKey', $this->controller->_key);
ab435bd4 555
6a488035
TO
556 }
557
ab435bd4
DL
558 // _generateQFKey suppresses the qfKey generation on form snippets that
559 // are part of other forms, hence we use that to avoid adding entryURL
560 if ($this->controller->_generateQFKey && $this->controller->_entryURL) {
3ab88a8c
DL
561 $this->addElement('hidden', 'entryURL', $this->controller->_entryURL);
562 }
6a488035
TO
563
564 $this->buildQuickForm();
565
566 $defaults = $this->setDefaultValues();
567 unset($defaults['qfKey']);
568
569 if (!empty($defaults)) {
570 $this->setDefaults($defaults);
571 }
572
573 // call the form hook
b44e3f84 574 // also call the hook function so any modules can set their own custom defaults
6a488035
TO
575 // the user can do both the form and set default values with this hook
576 CRM_Utils_Hook::buildForm(get_class($this), $this);
577
578 $this->addRules();
3e201321 579
580 //Set html data-attribute to enable warning user of unsaved changes
f9f40af3 581 if ($this->unsavedChangesWarn === TRUE
353ffa53
TO
582 || (!isset($this->unsavedChangesWarn)
583 && ($this->_action & CRM_Core_Action::ADD || $this->_action & CRM_Core_Action::UPDATE)
584 )
585 ) {
f9f40af3 586 $this->setAttribute('data-warn-changes', 'true');
3e201321 587 }
6a488035
TO
588 }
589
590 /**
3bdf1f3a 591 * Add default Next / Back buttons.
6a488035 592 *
6c552737
TO
593 * @param array $params
594 * Array of associative arrays in the order in which the buttons should be
595 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
596 * The base form class will define a bunch of static arrays for commonly used
597 * formats.
6a488035 598 */
00be9182 599 public function addButtons($params) {
deae896d 600 $prevnext = $spacing = array();
6a488035 601 foreach ($params as $button) {
fdb0ca2c 602 $attrs = array('class' => 'crm-form-submit') + (array) CRM_Utils_Array::value('js', $button);
6a488035 603
b01812e5
CW
604 if (!empty($button['class'])) {
605 $attrs['class'] .= ' ' . $button['class'];
606 }
607
fdb0ca2c
CW
608 if (!empty($button['isDefault'])) {
609 $attrs['class'] .= ' default';
6a488035
TO
610 }
611
fdb0ca2c
CW
612 if (in_array($button['type'], array('upload', 'next', 'submit', 'done', 'process', 'refresh'))) {
613 $attrs['class'] .= ' validate';
f62db3ac 614 $defaultIcon = 'fa-check';
fdb0ca2c
CW
615 }
616 else {
b61fd8cf 617 $attrs['class'] .= ' cancel';
f62db3ac 618 $defaultIcon = $button['type'] == 'back' ? 'fa-chevron-left' : 'fa-times';
b61fd8cf
CW
619 }
620
6a488035
TO
621 if ($button['type'] === 'reset') {
622 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
623 }
624 else {
a7488080 625 if (!empty($button['subName'])) {
deae896d 626 if ($button['subName'] == 'new') {
f62db3ac 627 $defaultIcon = 'fa-plus-circle';
deae896d 628 }
fdb0ca2c 629 if ($button['subName'] == 'done') {
f62db3ac 630 $defaultIcon = 'fa-check-circle';
fdb0ca2c
CW
631 }
632 if ($button['subName'] == 'next') {
f62db3ac 633 $defaultIcon = 'fa-chevron-right';
fdb0ca2c 634 }
6a488035
TO
635 }
636
b61fd8cf 637 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
fdb0ca2c 638 $attrs['accesskey'] = 'S';
6a488035 639 }
deae896d
CW
640 $icon = CRM_Utils_Array::value('icon', $button, $defaultIcon);
641 if ($icon) {
642 $attrs['crm-icon'] = $icon;
643 }
fdb0ca2c 644 $buttonName = $this->getButtonName($button['type'], CRM_Utils_Array::value('subName', $button));
6a488035
TO
645 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
646 }
a7488080 647 if (!empty($button['isDefault'])) {
6a488035
TO
648 $this->setDefaultAction($button['type']);
649 }
650
651 // if button type is upload, set the enctype
652 if ($button['type'] == 'upload') {
653 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
654 $this->setMaxFileSize();
655 }
656
657 // hack - addGroup uses an array to express variable spacing, read from the last element
658 $spacing[] = CRM_Utils_Array::value('spacing', $button, self::ATTR_SPACING);
659 }
660 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
661 }
662
663 /**
fe482240 664 * Getter function for Name.
6a488035
TO
665 *
666 * @return string
6a488035 667 */
00be9182 668 public function getName() {
6a488035
TO
669 return $this->_name;
670 }
671
672 /**
fe482240 673 * Getter function for State.
6a488035
TO
674 *
675 * @return object
6a488035 676 */
00be9182 677 public function &getState() {
6a488035
TO
678 return $this->_state;
679 }
680
681 /**
fe482240 682 * Getter function for StateType.
6a488035
TO
683 *
684 * @return int
6a488035 685 */
00be9182 686 public function getStateType() {
6a488035
TO
687 return $this->_state->getType();
688 }
689
690 /**
3bdf1f3a 691 * Getter function for title.
692 *
693 * Should be over-ridden by derived class.
6a488035
TO
694 *
695 * @return string
6a488035 696 */
00be9182 697 public function getTitle() {
6a488035
TO
698 return $this->_title ? $this->_title : ts('ERROR: Title is not Set');
699 }
700
701 /**
100fef9d 702 * Setter function for title.
6a488035 703 *
6a0b768e
TO
704 * @param string $title
705 * The title of the form.
6a488035 706 */
00be9182 707 public function setTitle($title) {
6a488035
TO
708 $this->_title = $title;
709 }
710
8345c9d3
EM
711 /**
712 * Assign billing type id to bltID.
713 *
714 * @throws CRM_Core_Exception
715 */
716 public function assignBillingType() {
b576d770 717 $this->_bltID = CRM_Core_BAO_LocationType::getBilling();
8345c9d3
EM
718 $this->set('bltID', $this->_bltID);
719 $this->assign('bltID', $this->_bltID);
720 }
721
1b9f9ca3
EM
722 /**
723 * This if a front end form function for setting the payment processor.
724 *
725 * It would be good to sync it with the back-end function on abstractEditPayment & use one everywhere.
726 *
682c12c0 727 * @param bool $isPayLaterEnabled
cbcb5b49 728 *
1b9f9ca3
EM
729 * @throws \CRM_Core_Exception
730 */
682c12c0 731 protected function assignPaymentProcessor($isPayLaterEnabled) {
1b9f9ca3
EM
732 $this->_paymentProcessors = CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors(
733 array(ucfirst($this->_mode) . 'Mode'),
734 $this->_paymentProcessorIDs
735 );
682c12c0
JP
736 if ($isPayLaterEnabled) {
737 $this->_paymentProcessors[0] = CRM_Financial_BAO_PaymentProcessor::getPayment(0);
738 }
1b9f9ca3
EM
739
740 if (!empty($this->_paymentProcessors)) {
741 foreach ($this->_paymentProcessors as $paymentProcessorID => $paymentProcessorDetail) {
742 if (empty($this->_paymentProcessor) && $paymentProcessorDetail['is_default'] == 1 || (count($this->_paymentProcessors) == 1)
743 ) {
744 $this->_paymentProcessor = $paymentProcessorDetail;
745 $this->assign('paymentProcessor', $this->_paymentProcessor);
746 // Setting this is a bit of a legacy overhang.
747 $this->_paymentObject = $paymentProcessorDetail['object'];
748 }
749 }
750 // It's not clear why we set this on the form.
751 $this->set('paymentProcessors', $this->_paymentProcessors);
752 }
753 else {
754 throw new CRM_Core_Exception(ts('A payment processor configured for this page might be disabled (contact the site administrator for assistance).'));
755 }
f48e6cf7 756
1b9f9ca3
EM
757 }
758
bddc8a28 759 /**
760 * Format the fields for the payment processor.
761 *
762 * In order to pass fields to the payment processor in a consistent way we add some renamed
763 * parameters.
764 *
765 * @param array $fields
766 *
767 * @return array
768 */
769 protected function formatParamsForPaymentProcessor($fields) {
770 // also add location name to the array
771 $this->_params["address_name-{$this->_bltID}"] = CRM_Utils_Array::value('billing_first_name', $this->_params) . ' ' . CRM_Utils_Array::value('billing_middle_name', $this->_params) . ' ' . CRM_Utils_Array::value('billing_last_name', $this->_params);
772 $this->_params["address_name-{$this->_bltID}"] = trim($this->_params["address_name-{$this->_bltID}"]);
773 // Add additional parameters that the payment processors are used to receiving.
774 if (!empty($this->_params["billing_state_province_id-{$this->_bltID}"])) {
775 $this->_params['state_province'] = $this->_params["state_province-{$this->_bltID}"] = $this->_params["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($this->_params["billing_state_province_id-{$this->_bltID}"]);
776 }
777 if (!empty($this->_params["billing_country_id-{$this->_bltID}"])) {
778 $this->_params['country'] = $this->_params["country-{$this->_bltID}"] = $this->_params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($this->_params["billing_country_id-{$this->_bltID}"]);
779 }
780
781 list($hasAddressField, $addressParams) = CRM_Contribute_BAO_Contribution::getPaymentProcessorReadyAddressParams($this->_params, $this->_bltID);
782 if ($hasAddressField) {
783 $this->_params = array_merge($this->_params, $addressParams);
784 }
785
786 $nameFields = array('first_name', 'middle_name', 'last_name');
787 foreach ($nameFields as $name) {
788 $fields[$name] = 1;
789 if (array_key_exists("billing_$name", $this->_params)) {
790 $this->_params[$name] = $this->_params["billing_{$name}"];
791 $this->_params['preserveDBName'] = TRUE;
792 }
793 }
794 return $fields;
795 }
796
42e3a033
EM
797 /**
798 * Handle Payment Processor switching for contribution and event registration forms.
799 *
800 * This function is shared between contribution & event forms & this is their common class.
801 *
802 * However, this should be seen as an in-progress refactor, the end goal being to also align the
803 * backoffice forms that action payments.
804 *
805 * This function overlaps assignPaymentProcessor, in a bad way.
806 */
807 protected function preProcessPaymentOptions() {
808 $this->_paymentProcessorID = NULL;
809 if ($this->_paymentProcessors) {
810 if (!empty($this->_submitValues)) {
811 $this->_paymentProcessorID = CRM_Utils_Array::value('payment_processor_id', $this->_submitValues);
812 $this->_paymentProcessor = CRM_Utils_Array::value($this->_paymentProcessorID, $this->_paymentProcessors);
813 $this->set('type', $this->_paymentProcessorID);
814 $this->set('mode', $this->_mode);
815 $this->set('paymentProcessor', $this->_paymentProcessor);
816 }
817 // Set default payment processor
818 else {
819 foreach ($this->_paymentProcessors as $values) {
820 if (!empty($values['is_default']) || count($this->_paymentProcessors) == 1) {
821 $this->_paymentProcessorID = $values['id'];
822 break;
823 }
824 }
825 }
1d1fee72 826 if ($this->_paymentProcessorID
827 || (isset($this->_submitValues['payment_processor_id']) && $this->_submitValues['payment_processor_id'] == 0)
828 ) {
42e3a033
EM
829 CRM_Core_Payment_ProcessorForm::preProcess($this);
830 }
831 else {
832 $this->_paymentProcessor = array();
833 }
cb5962bd 834 CRM_Financial_Form_Payment::addCreditCardJs($this->_paymentProcessorID);
42e3a033
EM
835 }
836 $this->assign('paymentProcessorID', $this->_paymentProcessorID);
f48e6cf7 837 // We save the fact that the profile 'billing' is required on the payment form.
838 // Currently pay-later is the only 'processor' that takes notice of this - but ideally
839 // 1) it would be possible to select the minimum_billing_profile_id for the contribution form
840 // 2) that profile_id would be set on the payment processor
841 // 3) the payment processor would return a billing form that combines these user-configured
842 // minimums with the payment processor minimums. This would lead to fields like 'postal_code'
843 // only being on the form if either the admin has configured it as wanted or the processor
844 // requires it.
845 $this->assign('billing_profile_id', (CRM_Utils_Array::value('is_billing_required', $this->_values) ? 'billing' : ''));
42e3a033 846 }
1b9f9ca3 847
ec022878 848 /**
849 * Handle pre approval for processors.
850 *
851 * This fits with the flow where a pre-approval is done and then confirmed in the next stage when confirm is hit.
852 *
853 * This function is shared between contribution & event forms & this is their common class.
854 *
855 * However, this should be seen as an in-progress refactor, the end goal being to also align the
856 * backoffice forms that action payments.
857 *
858 * @param array $params
859 */
860 protected function handlePreApproval(&$params) {
861 try {
862 $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
863 $params['component'] = 'contribute';
864 $result = $payment->doPreApproval($params);
865 if (empty($result)) {
866 // This could happen, for example, when paypal looks at the button value & decides it is not paypal express.
867 return;
868 }
869 }
870 catch (\Civi\Payment\Exception\PaymentProcessorException $e) {
abfb35ee 871 CRM_Core_Error::statusBounce(ts('Payment approval failed with message :') . $e->getMessage(), $payment->getCancelUrl($params['qfKey'], CRM_Utils_Array::value('participant_id', $params)));
ec022878 872 }
873
874 $this->set('pre_approval_parameters', $result['pre_approval_parameters']);
875 if (!empty($result['redirect_url'])) {
876 CRM_Utils_System::redirect($result['redirect_url']);
877 }
878 }
879
6a488035 880 /**
fe482240 881 * Setter function for options.
6a488035 882 *
6c552737 883 * @param mixed $options
6a488035 884 */
00be9182 885 public function setOptions($options) {
6a488035
TO
886 $this->_options = $options;
887 }
888
6a488035 889 /**
fe482240 890 * Render form and return contents.
6a488035
TO
891 *
892 * @return string
6a488035 893 */
00be9182 894 public function toSmarty() {
1d07e7ab 895 $this->preProcessChainSelectFields();
6a488035
TO
896 $renderer = $this->getRenderer();
897 $this->accept($renderer);
898 $content = $renderer->toArray();
899 $content['formName'] = $this->getName();
b50fdacc
CW
900 // CRM-15153
901 $content['formClass'] = CRM_Utils_System::getClassName($this);
6a488035
TO
902 return $content;
903 }
904
905 /**
3bdf1f3a 906 * Getter function for renderer.
907 *
908 * If renderer is not set create one and initialize it.
6a488035
TO
909 *
910 * @return object
6a488035 911 */
00be9182 912 public function &getRenderer() {
6a488035
TO
913 if (!isset($this->_renderer)) {
914 $this->_renderer = CRM_Core_Form_Renderer::singleton();
915 }
916 return $this->_renderer;
917 }
918
919 /**
fe482240 920 * Use the form name to create the tpl file name.
6a488035
TO
921 *
922 * @return string
6a488035 923 */
00be9182 924 public function getTemplateFileName() {
6a488035
TO
925 $ext = CRM_Extension_System::singleton()->getMapper();
926 if ($ext->isExtensionClass(CRM_Utils_System::getClassName($this))) {
927 $filename = $ext->getTemplateName(CRM_Utils_System::getClassName($this));
928 $tplname = $ext->getTemplatePath(CRM_Utils_System::getClassName($this)) . DIRECTORY_SEPARATOR . $filename;
929 }
930 else {
9b591d79
TO
931 $tplname = strtr(
932 CRM_Utils_System::getClassName($this),
933 array(
934 '_' => DIRECTORY_SEPARATOR,
935 '\\' => DIRECTORY_SEPARATOR,
936 )
937 ) . '.tpl';
6a488035
TO
938 }
939 return $tplname;
940 }
941
8aac22c8 942 /**
3bdf1f3a 943 * A wrapper for getTemplateFileName.
944 *
945 * This includes calling the hook to prevent us from having to copy & paste the logic of calling the hook.
8aac22c8 946 */
00be9182 947 public function getHookedTemplateFileName() {
8aac22c8 948 $pageTemplateFile = $this->getTemplateFileName();
949 CRM_Utils_Hook::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
950 return $pageTemplateFile;
951 }
952
6a488035 953 /**
3bdf1f3a 954 * Default extra tpl file basically just replaces .tpl with .extra.tpl.
955 *
956 * i.e. we do not override.
6a488035
TO
957 *
958 * @return string
6a488035 959 */
00be9182 960 public function overrideExtraTemplateFileName() {
6a488035
TO
961 return NULL;
962 }
963
964 /**
fe482240 965 * Error reporting mechanism.
6a488035 966 *
6a0b768e
TO
967 * @param string $message
968 * Error Message.
969 * @param int $code
970 * Error Code.
971 * @param CRM_Core_DAO $dao
972 * A data access object on which we perform a rollback if non - empty.
6a488035 973 */
00be9182 974 public function error($message, $code = NULL, $dao = NULL) {
6a488035
TO
975 if ($dao) {
976 $dao->query('ROLLBACK');
977 }
978
979 $error = CRM_Core_Error::singleton();
980
981 $error->push($code, $message);
982 }
983
984 /**
fe482240 985 * Store the variable with the value in the form scope.
6a488035 986 *
6c552737
TO
987 * @param string $name
988 * Name of the variable.
989 * @param mixed $value
990 * Value of the variable.
6a488035 991 */
00be9182 992 public function set($name, $value) {
6a488035
TO
993 $this->controller->set($name, $value);
994 }
995
996 /**
fe482240 997 * Get the variable from the form scope.
6a488035 998 *
6c552737
TO
999 * @param string $name
1000 * Name of the variable
6a488035
TO
1001 *
1002 * @return mixed
6a488035 1003 */
00be9182 1004 public function get($name) {
6a488035
TO
1005 return $this->controller->get($name);
1006 }
1007
1008 /**
fe482240 1009 * Getter for action.
6a488035
TO
1010 *
1011 * @return int
6a488035 1012 */
00be9182 1013 public function getAction() {
6a488035
TO
1014 return $this->_action;
1015 }
1016
1017 /**
fe482240 1018 * Setter for action.
6a488035 1019 *
6a0b768e
TO
1020 * @param int $action
1021 * The mode we want to set the form.
6a488035 1022 */
00be9182 1023 public function setAction($action) {
6a488035
TO
1024 $this->_action = $action;
1025 }
1026
1027 /**
fe482240 1028 * Assign value to name in template.
6a488035 1029 *
6a0b768e
TO
1030 * @param string $var
1031 * Name of variable.
1032 * @param mixed $value
1033 * Value of variable.
6a488035 1034 */
00be9182 1035 public function assign($var, $value = NULL) {
6a488035
TO
1036 self::$_template->assign($var, $value);
1037 }
1038
1039 /**
fe482240 1040 * Assign value to name in template by reference.
6a488035 1041 *
6a0b768e
TO
1042 * @param string $var
1043 * Name of variable.
1044 * @param mixed $value
8eedd10a 1045 * Value of variable.
6a488035 1046 */
00be9182 1047 public function assign_by_ref($var, &$value) {
6a488035
TO
1048 self::$_template->assign_by_ref($var, $value);
1049 }
1050
4a9538ac 1051 /**
fe482240 1052 * Appends values to template variables.
4a9538ac
CW
1053 *
1054 * @param array|string $tpl_var the template variable name(s)
6a0b768e
TO
1055 * @param mixed $value
1056 * The value to append.
4a9538ac
CW
1057 * @param bool $merge
1058 */
f9f40af3 1059 public function append($tpl_var, $value = NULL, $merge = FALSE) {
4a9538ac
CW
1060 self::$_template->append($tpl_var, $value, $merge);
1061 }
1062
1063 /**
fe482240 1064 * Returns an array containing template variables.
4a9538ac
CW
1065 *
1066 * @param string $name
2a6da8d7 1067 *
4a9538ac
CW
1068 * @return array
1069 */
f9f40af3 1070 public function get_template_vars($name = NULL) {
4a9538ac
CW
1071 return self::$_template->get_template_vars($name);
1072 }
1073
a0ee3941 1074 /**
100fef9d 1075 * @param string $name
a0ee3941
EM
1076 * @param $title
1077 * @param $values
1078 * @param array $attributes
1079 * @param null $separator
1080 * @param bool $required
1081 *
1082 * @return HTML_QuickForm_group
1083 */
00be9182 1084 public function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
6a488035 1085 $options = array();
8a4f27dc 1086 $attributes = $attributes ? $attributes : array();
b847e6e7
CW
1087 $allowClear = !empty($attributes['allowClear']);
1088 unset($attributes['allowClear']);
385f11fd 1089 $attributes['id_suffix'] = $name;
6a488035
TO
1090 foreach ($values as $key => $var) {
1091 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
1092 }
1093 $group = $this->addGroup($options, $name, $title, $separator);
3ef93345
MD
1094
1095 $optionEditKey = 'data-option-edit-path';
1096 if (!empty($attributes[$optionEditKey])) {
1097 $group->setAttribute($optionEditKey, $attributes[$optionEditKey]);
1098 }
1099
6a488035
TO
1100 if ($required) {
1101 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
1102 }
b847e6e7
CW
1103 if ($allowClear) {
1104 $group->setAttribute('allowClear', TRUE);
8a4f27dc 1105 }
6a488035
TO
1106 return $group;
1107 }
1108
a0ee3941 1109 /**
100fef9d 1110 * @param int $id
a0ee3941
EM
1111 * @param $title
1112 * @param bool $allowClear
1113 * @param null $required
1114 * @param array $attributes
1115 */
00be9182 1116 public function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
8a4f27dc 1117 $attributes += array('id_suffix' => $id);
353ffa53 1118 $choice = array();
8a4f27dc
CW
1119 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
1120 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
6a488035 1121
8a4f27dc 1122 $group = $this->addGroup($choice, $id, $title);
b847e6e7
CW
1123 if ($allowClear) {
1124 $group->setAttribute('allowClear', TRUE);
8a4f27dc 1125 }
6a488035
TO
1126 if ($required) {
1127 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
1128 }
1129 }
1130
a0ee3941 1131 /**
100fef9d 1132 * @param int $id
a0ee3941
EM
1133 * @param $title
1134 * @param $values
1135 * @param null $other
1136 * @param null $attributes
1137 * @param null $required
1138 * @param null $javascriptMethod
1139 * @param string $separator
1140 * @param bool $flipValues
1141 */
2da40d21 1142 public function addCheckBox(
f9f40af3
TO
1143 $id, $title, $values, $other = NULL,
1144 $attributes = NULL, $required = NULL,
6a488035 1145 $javascriptMethod = NULL,
f9f40af3 1146 $separator = '<br />', $flipValues = FALSE
6a488035
TO
1147 ) {
1148 $options = array();
1149
1150 if ($javascriptMethod) {
1151 foreach ($values as $key => $var) {
1152 if (!$flipValues) {
3ef93345 1153 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod, $attributes);
6a488035
TO
1154 }
1155 else {
3ef93345 1156 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod, $attributes);
6a488035
TO
1157 }
1158 }
1159 }
1160 else {
1161 foreach ($values as $key => $var) {
1162 if (!$flipValues) {
3ef93345 1163 $options[] = $this->createElement('checkbox', $var, NULL, $key, $attributes);
6a488035
TO
1164 }
1165 else {
3ef93345 1166 $options[] = $this->createElement('checkbox', $key, NULL, $var, $attributes);
6a488035
TO
1167 }
1168 }
1169 }
1170
3ef93345
MD
1171 $group = $this->addGroup($options, $id, $title, $separator);
1172 $optionEditKey = 'data-option-edit-path';
1173 if (!empty($attributes[$optionEditKey])) {
1174 $group->setAttribute($optionEditKey, $attributes[$optionEditKey]);
1175 }
6a488035
TO
1176
1177 if ($other) {
1178 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
1179 }
1180
1181 if ($required) {
1182 $this->addRule($id,
1183 ts('%1 is a required field.', array(1 => $title)),
1184 'required'
1185 );
1186 }
1187 }
1188
00be9182 1189 public function resetValues() {
6a488035
TO
1190 $data = $this->controller->container();
1191 $data['values'][$this->_name] = array();
1192 }
1193
1194 /**
100fef9d 1195 * Simple shell that derived classes can call to add buttons to
6a488035
TO
1196 * the form with a customized title for the main Submit
1197 *
6a0b768e
TO
1198 * @param string $title
1199 * Title of the main button.
1200 * @param string $nextType
1201 * Button type for the form after processing.
fd31fa4c
EM
1202 * @param string $backType
1203 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
6a488035 1204 */
00be9182 1205 public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
6a488035
TO
1206 $buttons = array();
1207 if ($backType != NULL) {
1208 $buttons[] = array(
1209 'type' => $backType,
1210 'name' => ts('Previous'),
1211 );
1212 }
1213 if ($nextType != NULL) {
1214 $nextButton = array(
1215 'type' => $nextType,
1216 'name' => $title,
1217 'isDefault' => TRUE,
1218 );
1219 if ($submitOnce) {
1220 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
1221 }
1222 $buttons[] = $nextButton;
1223 }
1224 $this->addButtons($buttons);
1225 }
1226
a0ee3941 1227 /**
100fef9d 1228 * @param string $name
a0ee3941
EM
1229 * @param string $from
1230 * @param string $to
1231 * @param string $label
1232 * @param string $dateFormat
1233 * @param bool $required
1234 * @param bool $displayTime
1235 */
00be9182 1236 public function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
6a488035
TO
1237 if ($displayTime) {
1238 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
1239 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
0db6c3e1
TO
1240 }
1241 else {
6a488035
TO
1242 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
1243 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1244 }
1245 }
d5965a37 1246
03225ad6
CW
1247 /**
1248 * Based on form action, return a string representing the api action.
1249 * Used by addField method.
1250 *
1251 * Return string
1252 */
1253 private function getApiAction() {
1254 $action = $this->getAction();
1255 if ($action & (CRM_Core_Action::UPDATE + CRM_Core_Action::ADD)) {
1256 return 'create';
1257 }
889dbed8 1258 if ($action & (CRM_Core_Action::VIEW + CRM_Core_Action::BROWSE + CRM_Core_Action::BASIC + CRM_Core_Action::ADVANCED + CRM_Core_Action::PREVIEW)) {
03225ad6
CW
1259 return 'get';
1260 }
1261 // If you get this exception try adding more cases above.
0e02cb01 1262 throw new Exception("Cannot determine api action for " . get_class($this) . '.' . 'CRM_Core_Action "' . CRM_Core_Action::description($action) . '" not recognized.');
03225ad6
CW
1263 }
1264
6e62b28c 1265 /**
d5965a37 1266 * Classes extending CRM_Core_Form should implement this method.
6e62b28c
TM
1267 * @throws Exception
1268 */
1269 public function getDefaultEntity() {
0e02cb01 1270 throw new Exception("Cannot determine default entity. " . get_class($this) . " should implement getDefaultEntity().");
6e62b28c 1271 }
6a488035 1272
1ae720b3
TM
1273 /**
1274 * Classes extending CRM_Core_Form should implement this method.
1275 *
1276 * TODO: Merge with CRM_Core_DAO::buildOptionsContext($context) and add validation.
1277 * @throws Exception
1278 */
1279 public function getDefaultContext() {
0e02cb01 1280 throw new Exception("Cannot determine default context. " . get_class($this) . " should implement getDefaultContext().");
1ae720b3
TM
1281 }
1282
5fafc9b0 1283 /**
fe482240 1284 * Adds a select based on field metadata.
5fafc9b0 1285 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
475e9f44 1286 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
6a0b768e
TO
1287 * @param $name
1288 * Field name to go on the form.
1289 * @param array $props
1290 * Mix of html attributes and special properties, namely.
920600e1
CW
1291 * - entity (api entity name, can usually be inferred automatically from the form class)
1292 * - field (field name - only needed if different from name used on the form)
1293 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
1294 * - placeholder - set to NULL to disable
d0def949 1295 * - multiple - bool
76773c5a 1296 * - context - @see CRM_Core_DAO::buildOptionsContext
5fafc9b0
CW
1297 * @param bool $required
1298 * @throws CRM_Core_Exception
1299 * @return HTML_QuickForm_Element
1300 */
00be9182 1301 public function addSelect($name, $props = array(), $required = FALSE) {
920600e1 1302 if (!isset($props['entity'])) {
6e62b28c 1303 $props['entity'] = $this->getDefaultEntity();
6a488035 1304 }
920600e1
CW
1305 if (!isset($props['field'])) {
1306 $props['field'] = strrpos($name, '[') ? rtrim(substr($name, 1 + strrpos($name, '[')), ']') : $name;
e869b07d 1307 }
65e8615b
CW
1308 if (!isset($props['context'])) {
1309 try {
1310 $props['context'] = $this->getDefaultContext();
1311 }
1312 // This is not a required param, so we'll ignore if this doesn't exist.
1313 catch (Exception $e) {}
1314 }
f76b27fe
CW
1315 // Fetch options from the api unless passed explicitly
1316 if (isset($props['options'])) {
1317 $options = $props['options'];
1318 }
1319 else {
76352fbc 1320 $info = civicrm_api3($props['entity'], 'getoptions', $props);
f76b27fe
CW
1321 $options = $info['values'];
1322 }
5fafc9b0 1323 if (!array_key_exists('placeholder', $props)) {
76773c5a 1324 $props['placeholder'] = $required ? ts('- select -') : CRM_Utils_Array::value('context', $props) == 'search' ? ts('- any -') : ts('- none -');
5fafc9b0 1325 }
5fafc9b0
CW
1326 // Handle custom field
1327 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1328 list(, $id) = explode('_', $name);
1329 $label = isset($props['label']) ? $props['label'] : CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
475e9f44 1330 $gid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
76773c5a
CW
1331 if (CRM_Utils_Array::value('context', $props) != 'search') {
1332 $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);
1333 }
5fafc9b0
CW
1334 }
1335 // Core field
6a488035 1336 else {
f76b27fe 1337 $info = civicrm_api3($props['entity'], 'getfields');
22e263ad 1338 foreach ($info['values'] as $uniqueName => $fieldSpec) {
e869b07d 1339 if (
920600e1
CW
1340 $uniqueName === $props['field'] ||
1341 CRM_Utils_Array::value('name', $fieldSpec) === $props['field'] ||
1342 in_array($props['field'], CRM_Utils_Array::value('api.aliases', $fieldSpec, array()))
e869b07d
CW
1343 ) {
1344 break;
1345 }
6a488035 1346 }
e869b07d 1347 $label = isset($props['label']) ? $props['label'] : $fieldSpec['title'];
76773c5a 1348 if (CRM_Utils_Array::value('context', $props) != 'search') {
599ae208 1349 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec);
76773c5a 1350 }
6a488035 1351 }
920600e1
CW
1352 $props['class'] = (isset($props['class']) ? $props['class'] . ' ' : '') . "crm-select2";
1353 $props['data-api-entity'] = $props['entity'];
1354 $props['data-api-field'] = $props['field'];
76773c5a 1355 CRM_Utils_Array::remove($props, 'label', 'entity', 'field', 'option_url', 'options', 'context');
5fafc9b0 1356 return $this->add('select', $name, $label, $options, $required, $props);
6a488035
TO
1357 }
1358
7ec4548b
TM
1359 /**
1360 * Adds a field based on metadata.
1361 *
1362 * @param $name
1363 * Field name to go on the form.
1364 * @param array $props
1365 * Mix of html attributes and special properties, namely.
1366 * - entity (api entity name, can usually be inferred automatically from the form class)
03225ad6 1367 * - name (field name - only needed if different from name used on the form)
7ec4548b
TM
1368 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
1369 * - placeholder - set to NULL to disable
1370 * - multiple - bool
1371 * - context - @see CRM_Core_DAO::buildOptionsContext
1372 * @param bool $required
ed0ca248 1373 * @param bool $legacyDate
1374 * Temporary param to facilitate the conversion of fields to use the datepicker in
1375 * a controlled way. To convert the field the jcalendar code needs to be removed from the
1376 * tpl as well. That file is intended to be EOL.
1377 *
03225ad6
CW
1378 * @throws \CiviCRM_API3_Exception
1379 * @throws \Exception
2a300b65 1380 * @return HTML_QuickForm_Element
7ec4548b 1381 */
ed0ca248 1382 public function addField($name, $props = array(), $required = FALSE, $legacyDate = TRUE) {
1ae720b3 1383 // Resolve context.
916b6181 1384 if (empty($props['context'])) {
1ae720b3
TM
1385 $props['context'] = $this->getDefaultContext();
1386 }
916b6181 1387 $context = $props['context'];
7ec4548b 1388 // Resolve entity.
916b6181 1389 if (empty($props['entity'])) {
7ec4548b
TM
1390 $props['entity'] = $this->getDefaultEntity();
1391 }
1392 // Resolve field.
916b6181 1393 if (empty($props['name'])) {
03225ad6 1394 $props['name'] = strrpos($name, '[') ? rtrim(substr($name, 1 + strrpos($name, '[')), ']') : $name;
7ec4548b 1395 }
03225ad6 1396 // Resolve action.
916b6181 1397 if (empty($props['action'])) {
03225ad6 1398 $props['action'] = $this->getApiAction();
7ec4548b 1399 }
2b31bc15
CW
1400
1401 // Handle custom fields
1402 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1403 $fieldId = (int) substr($name, 7);
916b6181 1404 return CRM_Core_BAO_CustomField::addQuickFormElement($this, $name, $fieldId, $required, $context == 'search', CRM_Utils_Array::value('label', $props));
2b31bc15
CW
1405 }
1406
1407 // Core field - get metadata.
d60a6fba 1408 $fieldSpec = civicrm_api3($props['entity'], 'getfield', $props);
03225ad6 1409 $fieldSpec = $fieldSpec['values'];
a4969aee 1410 $label = CRM_Utils_Array::value('label', $props, isset($fieldSpec['title']) ? $fieldSpec['title'] : NULL);
7ec4548b 1411
7ec4548b 1412 $widget = isset($props['type']) ? $props['type'] : $fieldSpec['html']['type'];
916b6181 1413 if ($widget == 'TextArea' && $context == 'search') {
7ec4548b
TM
1414 $widget = 'Text';
1415 }
1416
1417 $isSelect = (in_array($widget, array(
1418 'Select',
dd4706ef
TM
1419 'CheckBoxGroup',
1420 'RadioGroup',
7ec4548b
TM
1421 'Radio',
1422 )));
1423
1424 if ($isSelect) {
2f32ed10 1425 // Fetch options from the api unless passed explicitly.
7ec4548b
TM
1426 if (isset($props['options'])) {
1427 $options = $props['options'];
1428 }
1429 else {
a4969aee 1430 $options = isset($fieldSpec['options']) ? $fieldSpec['options'] : NULL;
7ec4548b 1431 }
916b6181 1432 if ($context == 'search') {
7ec4548b 1433 $widget = 'Select';
65e8615b 1434 $props['multiple'] = CRM_Utils_Array::value('multiple', $props, TRUE);
7ec4548b 1435 }
7ec4548b
TM
1436
1437 // Add data for popup link.
3ef93345
MD
1438 $canEditOptions = CRM_Core_Permission::check('administer CiviCRM');
1439 $hasOptionUrl = !empty($props['option_url']);
1440 $optionUrlKeyIsSet = array_key_exists('option_url', $props);
1441 $shouldAdd = $context !== 'search' && $isSelect && $canEditOptions;
1442
1443 // Only add if key is not set, or if non-empty option url is provided
1444 if (($hasOptionUrl || !$optionUrlKeyIsSet) && $shouldAdd) {
1445 $optionUrl = $hasOptionUrl ? $props['option_url'] :
1446 CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec);
1447 $props['data-option-edit-path'] = $optionUrl;
7ec4548b 1448 $props['data-api-entity'] = $props['entity'];
03225ad6 1449 $props['data-api-field'] = $props['name'];
7ec4548b
TM
1450 }
1451 }
95457d69 1452 $props += CRM_Utils_Array::value('html', $fieldSpec, array());
65e8615b 1453 CRM_Utils_Array::remove($props, 'entity', 'name', 'context', 'label', 'action', 'type', 'option_url', 'options');
599ae208 1454
b44e3f84 1455 // TODO: refactor switch statement, to separate methods.
7ec4548b
TM
1456 switch ($widget) {
1457 case 'Text':
d8f1758d
CW
1458 case 'Url':
1459 case 'Number':
1460 case 'Email':
7ec4548b 1461 //TODO: Autodetect ranges
5b8080ad 1462 $props['size'] = isset($props['size']) ? $props['size'] : 60;
d8f1758d 1463 return $this->add(strtolower($widget), $name, $label, $props, $required);
7ec4548b 1464
b4b53245 1465 case 'hidden':
2a300b65 1466 return $this->add('hidden', $name, NULL, $props, $required);
b4b53245 1467
0efbca68
TM
1468 case 'TextArea':
1469 //Set default columns and rows for textarea.
1470 $props['rows'] = isset($props['rows']) ? $props['rows'] : 4;
1471 $props['cols'] = isset($props['cols']) ? $props['cols'] : 60;
599ae208 1472 return $this->add('textarea', $name, $label, $props, $required);
0efbca68 1473
db3ec100 1474 case 'Select Date':
ed0ca248 1475 // This is a white list for fields that have been tested with
1476 // date picker. We should be able to remove the other
1477 if ($legacyDate) {
1478 //TODO: add range support
1479 //TODO: Add date formats
1480 //TODO: Add javascript template for dates.
1481 return $this->addDate($name, $label, $required, $props);
1482 }
1483 else {
1484 $fieldSpec = CRM_Utils_Date::addDateMetadataToField($fieldSpec, $fieldSpec);
1485 $attributes = array('format' => $fieldSpec['date_format']);
1486 return $this->add('datepicker', $name, $label, $attributes, $required, $fieldSpec['datepicker']['extra']);
1487 }
db3ec100 1488
a4969aee
TM
1489 case 'Radio':
1490 $separator = isset($props['separator']) ? $props['separator'] : NULL;
125d54e1 1491 unset($props['separator']);
ef3a048a 1492 if (!isset($props['allowClear'])) {
125d54e1 1493 $props['allowClear'] = !$required;
ef3a048a 1494 }
2a300b65 1495 return $this->addRadio($name, $label, $options, $props, $separator, $required);
a4969aee 1496
b248d52b
CW
1497 case 'ChainSelect':
1498 $props += array(
1499 'required' => $required,
1500 'label' => $label,
916b6181 1501 'multiple' => $context == 'search',
b248d52b
CW
1502 );
1503 return $this->addChainSelect($name, $props);
1504
7ec4548b 1505 case 'Select':
b248d52b 1506 $props['class'] = CRM_Utils_Array::value('class', $props, 'big') . ' crm-select2';
65e8615b 1507 if (!array_key_exists('placeholder', $props)) {
78e1efac 1508 $props['placeholder'] = $required ? ts('- select -') : ($context == 'search' ? ts('- any -') : ts('- none -'));
7ec4548b 1509 }
7ec4548b 1510 // TODO: Add and/or option for fields that store multiple values
b2da03d6 1511 return $this->add('select', $name, $label, $options, $required, $props);
7ec4548b 1512
dd4706ef 1513 case 'CheckBoxGroup':
2a300b65 1514 return $this->addCheckBox($name, $label, array_flip($options), $required, $props);
dd4706ef
TM
1515
1516 case 'RadioGroup':
2a300b65 1517 return $this->addRadio($name, $label, $options, $props, NULL, $required);
dd4706ef 1518
a4969aee 1519 case 'CheckBox':
999ab5e1
TM
1520 $text = isset($props['text']) ? $props['text'] : NULL;
1521 unset($props['text']);
2a300b65 1522 return $this->addElement('checkbox', $name, $label, $text, $props);
a4969aee 1523
50471995 1524 //add support for 'Advcheckbox' field
1525 case 'advcheckbox':
b0964781 1526 $text = isset($props['text']) ? $props['text'] : NULL;
1527 unset($props['text']);
1528 return $this->addElement('advcheckbox', $name, $label, $text, $props);
50471995 1529
33fa033c
TM
1530 case 'File':
1531 // We should not build upload file in search mode.
916b6181 1532 if ($context == 'search') {
33fa033c
TM
1533 return;
1534 }
2a300b65 1535 $file = $this->add('file', $name, $label, $props, $required);
33fa033c 1536 $this->addUploadElement($name);
2a300b65 1537 return $file;
33fa033c 1538
b66c1d2c
CW
1539 case 'RichTextEditor':
1540 return $this->add('wysiwyg', $name, $label, $props, $required);
1541
b58770ea 1542 case 'EntityRef':
2a300b65 1543 return $this->addEntityRef($name, $label, $props, $required);
b58770ea 1544
e9bc5dcc
SL
1545 case 'Password':
1546 return $this->add('password', $name, $label, $props, $required);
1547
7ec4548b
TM
1548 // Check datatypes of fields
1549 // case 'Int':
1550 //case 'Float':
1551 //case 'Money':
7ec4548b
TM
1552 //case read only fields
1553 default:
1554 throw new Exception("Unsupported html-element " . $widget);
1555 }
1556 }
1557
6a488035
TO
1558 /**
1559 * Add a widget for selecting/editing/creating/copying a profile form
1560 *
6a0b768e
TO
1561 * @param string $name
1562 * HTML form-element name.
1563 * @param string $label
1564 * Printable label.
1565 * @param string $allowCoreTypes
1566 * Only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'.
1567 * @param string $allowSubTypes
1568 * Only present a UFGroup if its group_type is compatible with $allowSubypes.
6a488035 1569 * @param array $entities
6a0b768e
TO
1570 * @param bool $default
1571 * //CRM-15427.
54957108 1572 * @param string $usedFor
6a488035 1573 */
37375016 1574 public function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities, $default = FALSE, $usedFor = NULL) {
6a488035
TO
1575 // Output widget
1576 // FIXME: Instead of adhoc serialization, use a single json_encode()
1577 CRM_UF_Page_ProfileEditor::registerProfileScripts();
1578 CRM_UF_Page_ProfileEditor::registerSchemas(CRM_Utils_Array::collect('entity_type', $entities));
1579 $this->add('text', $name, $label, array(
1580 'class' => 'crm-profile-selector',
1581 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1582 'data-group-type' => CRM_Core_BAO_UFGroup::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1583 'data-entities' => json_encode($entities),
99e239bc 1584 //CRM-15427
1585 'data-default' => $default,
37375016 1586 'data-usedfor' => json_encode($usedFor),
6a488035
TO
1587 ));
1588 }
1589
a0ee3941
EM
1590 /**
1591 * @return null
1592 */
6a488035
TO
1593 public function getRootTitle() {
1594 return NULL;
1595 }
1596
a0ee3941
EM
1597 /**
1598 * @return string
1599 */
6a488035
TO
1600 public function getCompleteTitle() {
1601 return $this->getRootTitle() . $this->getTitle();
1602 }
1603
a0ee3941
EM
1604 /**
1605 * @return CRM_Core_Smarty
1606 */
00be9182 1607 public static function &getTemplate() {
6a488035
TO
1608 return self::$_template;
1609 }
1610
a0ee3941
EM
1611 /**
1612 * @param $elementName
1613 */
00be9182 1614 public function addUploadElement($elementName) {
6a488035
TO
1615 $uploadNames = $this->get('uploadNames');
1616 if (!$uploadNames) {
1617 $uploadNames = array();
1618 }
1619 if (is_array($elementName)) {
1620 foreach ($elementName as $name) {
1621 if (!in_array($name, $uploadNames)) {
1622 $uploadNames[] = $name;
1623 }
1624 }
1625 }
1626 else {
1627 if (!in_array($elementName, $uploadNames)) {
1628 $uploadNames[] = $elementName;
1629 }
1630 }
1631 $this->set('uploadNames', $uploadNames);
1632
1633 $config = CRM_Core_Config::singleton();
1634 if (!empty($uploadNames)) {
1635 $this->controller->addUploadAction($config->customFileUploadDir, $uploadNames);
1636 }
1637 }
1638
a0ee3941
EM
1639 /**
1640 * @param $name
1641 *
1642 * @return null
1643 */
00be9182 1644 public function getVar($name) {
6a488035
TO
1645 return isset($this->$name) ? $this->$name : NULL;
1646 }
1647
a0ee3941
EM
1648 /**
1649 * @param $name
1650 * @param $value
1651 */
00be9182 1652 public function setVar($name, $value) {
6a488035
TO
1653 $this->$name = $value;
1654 }
1655
1656 /**
fe482240 1657 * Add date.
6a488035 1658 *
013ac5df
CW
1659 * @deprecated
1660 * Use $this->add('datepicker', ...) instead.
a1a2a83d
TO
1661 *
1662 * @param string $name
1663 * Name of the element.
1664 * @param string $label
1665 * Label of the element.
6a0b768e
TO
1666 * @param bool $required
1667 * True if required.
a1a2a83d
TO
1668 * @param array $attributes
1669 * Key / value pair.
6a488035 1670 */
00be9182 1671 public function addDate($name, $label, $required = FALSE, $attributes = NULL) {
a7488080 1672 if (!empty($attributes['formatType'])) {
6a488035
TO
1673 // get actual format
1674 $params = array('name' => $attributes['formatType']);
1675 $values = array();
1676
1677 // cache date information
1678 static $dateFormat;
1679 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
a7488080 1680 if (empty($dateFormat[$key])) {
6a488035
TO
1681 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1682 $dateFormat[$key] = $values;
1683 }
1684 else {
1685 $values = $dateFormat[$key];
1686 }
1687
1688 if ($values['date_format']) {
1689 $attributes['format'] = $values['date_format'];
1690 }
1691
a7488080 1692 if (!empty($values['time_format'])) {
6a488035
TO
1693 $attributes['timeFormat'] = $values['time_format'];
1694 }
1695 $attributes['startOffset'] = $values['start'];
1696 $attributes['endOffset'] = $values['end'];
1697 }
1698
1699 $config = CRM_Core_Config::singleton();
a7488080 1700 if (empty($attributes['format'])) {
6a488035
TO
1701 $attributes['format'] = $config->dateInputFormat;
1702 }
1703
1704 if (!isset($attributes['startOffset'])) {
1705 $attributes['startOffset'] = 10;
1706 }
1707
1708 if (!isset($attributes['endOffset'])) {
1709 $attributes['endOffset'] = 10;
1710 }
1711
1712 $this->add('text', $name, $label, $attributes);
1713
8cc574cf 1714 if (!empty($attributes['addTime']) || !empty($attributes['timeFormat'])) {
6a488035
TO
1715
1716 if (!isset($attributes['timeFormat'])) {
1717 $timeFormat = $config->timeInputFormat;
1718 }
1719 else {
1720 $timeFormat = $attributes['timeFormat'];
1721 }
1722
1723 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1724 if ($timeFormat) {
1725 $show24Hours = TRUE;
1726 if ($timeFormat == 1) {
1727 $show24Hours = FALSE;
1728 }
1729
1730 //CRM-6664 -we are having time element name
1731 //in either flat string or an array format.
1732 $elementName = $name . '_time';
1733 if (substr($name, -1) == ']') {
1734 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1735 }
1736
1737 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1738 }
1739 }
1740
1741 if ($required) {
1742 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
8cc574cf 1743 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
6a488035
TO
1744 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1745 }
1746 }
1747 }
1748
1749 /**
013ac5df
CW
1750 * Function that will add date and time.
1751 *
1752 * @deprecated
1753 * Use $this->add('datepicker', ...) instead.
54957108 1754 *
1755 * @param string $name
1756 * @param string $label
1757 * @param bool $required
1758 * @param null $attributes
6a488035 1759 */
00be9182 1760 public function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
6a488035
TO
1761 $addTime = array('addTime' => TRUE);
1762 if (is_array($attributes)) {
1763 $attributes = array_merge($attributes, $addTime);
1764 }
1765 else {
1766 $attributes = $addTime;
1767 }
1768
1769 $this->addDate($name, $label, $required, $attributes);
1770 }
1771
1772 /**
fe482240 1773 * Add a currency and money element to the form.
3bdf1f3a 1774 *
1775 * @param string $name
1776 * @param string $label
1777 * @param bool $required
1778 * @param null $attributes
1779 * @param bool $addCurrency
1780 * @param string $currencyName
1781 * @param null $defaultCurrency
1782 * @param bool $freezeCurrency
1783 *
1784 * @return \HTML_QuickForm_Element
6a488035 1785 */
2da40d21 1786 public function addMoney(
f9f40af3 1787 $name,
6a488035 1788 $label,
f9f40af3
TO
1789 $required = FALSE,
1790 $attributes = NULL,
1791 $addCurrency = TRUE,
1792 $currencyName = 'currency',
6a488035 1793 $defaultCurrency = NULL,
f9f40af3 1794 $freezeCurrency = FALSE
6a488035
TO
1795 ) {
1796 $element = $this->add('text', $name, $label, $attributes, $required);
1797 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1798
1799 if ($addCurrency) {
1800 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1801 }
1802
1803 return $element;
1804 }
1805
1806 /**
fe482240 1807 * Add currency element to the form.
54957108 1808 *
1809 * @param string $name
1810 * @param null $label
1811 * @param bool $required
1812 * @param string $defaultCurrency
1813 * @param bool $freezeCurrency
483a53a8 1814 * @param bool $setDefaultCurrency
6a488035 1815 */
2da40d21 1816 public function addCurrency(
f9f40af3
TO
1817 $name = 'currency',
1818 $label = NULL,
1819 $required = TRUE,
6a488035 1820 $defaultCurrency = NULL,
483a53a8 1821 $freezeCurrency = FALSE,
1822 $setDefaultCurrency = TRUE
6a488035
TO
1823 ) {
1824 $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
b740ee4b
MW
1825 if (!array_key_exists($defaultCurrency, $currencies)) {
1826 Civi::log()->warning('addCurrency: Currency ' . $defaultCurrency . ' is disabled but still in use!');
1827 $currencies[$defaultCurrency] = $defaultCurrency;
1828 }
e1462487 1829 $options = array('class' => 'crm-select2 eight');
6a488035 1830 if (!$required) {
e1462487
CW
1831 $currencies = array('' => '') + $currencies;
1832 $options['placeholder'] = ts('- none -');
6a488035 1833 }
e1462487 1834 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
6a488035
TO
1835 if ($freezeCurrency) {
1836 $ele->freeze();
1837 }
1838 if (!$defaultCurrency) {
1839 $config = CRM_Core_Config::singleton();
1840 $defaultCurrency = $config->defaultCurrency;
1841 }
483a53a8 1842 // In some case, setting currency field by default might override the default value
1843 // as encountered in CRM-20527 for batch data entry
1844 if ($setDefaultCurrency) {
1845 $this->setDefaults(array($name => $defaultCurrency));
1846 }
6a488035
TO
1847 }
1848
47f21f3a 1849 /**
fe482240 1850 * Create a single or multiple entity ref field.
47f21f3a
CW
1851 * @param string $name
1852 * @param string $label
6a0b768e
TO
1853 * @param array $props
1854 * Mix of html and widget properties, including:.
16b10e64
CW
1855 * - select - params to give to select2 widget
1856 * - entity - defaults to contact
1857 * - create - can the user create a new entity on-the-fly?
79ae07d9 1858 * Set to TRUE if entity is contact and you want the default profiles,
b9aa8f56 1859 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
353ea873 1860 * note that permissions are checked automatically
16b10e64 1861 * - api - array of settings for the getlist api wrapper
353ea873 1862 * note that it accepts a 'params' setting which will be passed to the underlying api
16b10e64
CW
1863 * - placeholder - string
1864 * - multiple - bool
1865 * - class, etc. - other html properties
fd36866a 1866 * @param bool $required
79ae07d9 1867 *
47f21f3a
CW
1868 * @return HTML_QuickForm_Element
1869 */
00be9182 1870 public function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
a88cf11a 1871 require_once "api/api.php";
c66581f5 1872 $config = CRM_Core_Config::singleton();
76ec9ca7 1873 // Default properties
704d3260 1874 $props['api'] = CRM_Utils_Array::value('api', $props, array());
a88cf11a
CW
1875 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array::value('entity', $props, 'contact'));
1876 $props['class'] = ltrim(CRM_Utils_Array::value('class', $props, '') . ' crm-form-entityref');
47f21f3a 1877
79ae07d9
CW
1878 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission::check('edit all contacts') || CRM_Core_Permission::check('add contacts'))) {
1879 unset($props['create']);
1880 }
79ae07d9 1881
a88cf11a
CW
1882 $props['placeholder'] = CRM_Utils_Array::value('placeholder', $props, $required ? ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1883
1884 $defaults = array();
1885 if (!empty($props['multiple'])) {
1886 $defaults['multiple'] = TRUE;
79ae07d9
CW
1887 }
1888 $props['select'] = CRM_Utils_Array::value('select', $props, array()) + $defaults;
47f21f3a 1889
f9585de5 1890 $this->formatReferenceFieldAttributes($props, get_class($this));
47f21f3a
CW
1891 return $this->add('text', $name, $label, $props, $required);
1892 }
1893
1894 /**
f9585de5 1895 * @param array $props
1896 * @param string $formName
47f21f3a 1897 */
f9585de5 1898 private function formatReferenceFieldAttributes(&$props, $formName) {
1899 CRM_Utils_Hook::alterEntityRefParams($props, $formName);
47f21f3a 1900 $props['data-select-params'] = json_encode($props['select']);
76ec9ca7
CW
1901 $props['data-api-params'] = $props['api'] ? json_encode($props['api']) : NULL;
1902 $props['data-api-entity'] = $props['entity'];
79ae07d9
CW
1903 if (!empty($props['create'])) {
1904 $props['data-create-links'] = json_encode($props['create']);
47f21f3a 1905 }
a88cf11a 1906 CRM_Utils_Array::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
47f21f3a
CW
1907 }
1908
5d86176b 1909 /**
1910 * Convert all date fields within the params to mysql date ready for the
1911 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1912 * and if time is defined it is incorporated
1913 *
6a0b768e
TO
1914 * @param array $params
1915 * Input params from the form.
5d86176b 1916 *
1917 * @todo it would probably be better to work on $this->_params than a passed array
1918 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1919 * handling from BAO
1920 */
9b873358
TO
1921 public function convertDateFieldsToMySQL(&$params) {
1922 foreach ($this->_dateFields as $fieldName => $specs) {
1923 if (!empty($params[$fieldName])) {
5d86176b 1924 $params[$fieldName] = CRM_Utils_Date::isoToMysql(
1925 CRM_Utils_Date::processDate(
353ffa53
TO
1926 $params[$fieldName],
1927 CRM_Utils_Array::value("{$fieldName}_time", $params), TRUE)
5d86176b 1928 );
1929 }
92e4c2a5 1930 else {
9b873358 1931 if (isset($specs['default'])) {
5d86176b 1932 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1933 }
1934 }
1935 }
1936 }
1937
a0ee3941
EM
1938 /**
1939 * @param $elementName
1940 */
00be9182 1941 public function removeFileRequiredRules($elementName) {
6a488035
TO
1942 $this->_required = array_diff($this->_required, array($elementName));
1943 if (isset($this->_rules[$elementName])) {
1944 foreach ($this->_rules[$elementName] as $index => $ruleInfo) {
1945 if ($ruleInfo['type'] == 'uploadedfile') {
1946 unset($this->_rules[$elementName][$index]);
1947 }
1948 }
1949 if (empty($this->_rules[$elementName])) {
1950 unset($this->_rules[$elementName]);
1951 }
1952 }
1953 }
1954
1955 /**
fe482240 1956 * Function that can be defined in Form to override or.
6a488035 1957 * perform specific action on cancel action
6a488035 1958 */
f9f40af3
TO
1959 public function cancelAction() {
1960 }
7cb3d4f0
CW
1961
1962 /**
fe482240 1963 * Helper function to verify that required fields have been filled.
3bdf1f3a 1964 *
7cb3d4f0 1965 * Typically called within the scope of a FormRule function
3bdf1f3a 1966 *
1967 * @param array $fields
1968 * @param array $values
1969 * @param array $errors
7cb3d4f0 1970 */
00be9182 1971 public static function validateMandatoryFields($fields, $values, &$errors) {
7cb3d4f0
CW
1972 foreach ($fields as $name => $fld) {
1973 if (!empty($fld['is_required']) && CRM_Utils_System::isNull(CRM_Utils_Array::value($name, $values))) {
1974 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1975 }
1976 }
1977 }
da8d9879 1978
aa1b1481
EM
1979 /**
1980 * Get contact if for a form object. Prioritise
16b10e64 1981 * - cid in URL if 0 (on behalf on someoneelse)
aa1b1481 1982 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
16b10e64
CW
1983 * - logged in user id if it matches the one in the cid in the URL
1984 * - contact id validated from a checksum from a checksum
1985 * - cid from the url if the caller has ACL permission to view
1986 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
aa1b1481 1987 *
5c766a0b 1988 * @return NULL|int
aa1b1481 1989 */
8d388047 1990 protected function setContactID() {
da8d9879 1991 $tempID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
7b4d7ab8 1992 if (isset($this->_params) && !empty($this->_params['select_contact_id'])) {
596bff78 1993 $tempID = $this->_params['select_contact_id'];
1994 }
22e263ad 1995 if (isset($this->_params, $this->_params[0]) && !empty($this->_params[0]['select_contact_id'])) {
e1ce628e 1996 // event form stores as an indexed array, contribution form not so much...
1997 $tempID = $this->_params[0]['select_contact_id'];
1998 }
c156d4d6 1999
da8d9879 2000 // force to ignore the authenticated user
c156d4d6
E
2001 if ($tempID === '0' || $tempID === 0) {
2002 // we set the cid on the form so that this will be retained for the Confirm page
2003 // in the multi-page form & prevent us returning the $userID when this is called
2004 // from that page
2005 // we don't really need to set it when $tempID is set because the params have that stored
2006 $this->set('cid', 0);
11cf613f 2007 CRM_Core_Resources::singleton()->addVars('coreForm', array('contact_id' => (int) $tempID));
aa288d3f 2008 return (int) $tempID;
da8d9879
DG
2009 }
2010
596bff78 2011 $userID = $this->getLoggedInUserContactID();
da8d9879 2012
18406494 2013 if (!is_null($tempID) && $tempID === $userID) {
11cf613f 2014 CRM_Core_Resources::singleton()->addVars('coreForm', array('contact_id' => (int) $tempID));
aa288d3f 2015 return (int) $userID;
da8d9879
DG
2016 }
2017
2018 //check if this is a checksum authentication
2019 $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this);
2020 if ($userChecksum) {
2021 //check for anonymous user.
2022 $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($tempID, $userChecksum);
2023 if ($validUser) {
11cf613f 2024 CRM_Core_Resources::singleton()->addVars('coreForm', array('contact_id' => (int) $tempID));
2025 CRM_Core_Resources::singleton()->addVars('coreForm', array('checksum' => (int) $tempID));
da8d9879
DG
2026 return $tempID;
2027 }
2028 }
2029 // check if user has permission, CRM-12062
4c9b6178 2030 elseif ($tempID && CRM_Contact_BAO_Contact_Permission::allow($tempID)) {
11cf613f 2031 CRM_Core_Resources::singleton()->addVars('coreForm', array('contact_id' => (int) $tempID));
da8d9879
DG
2032 return $tempID;
2033 }
064af727 2034 if (is_numeric($userID)) {
2035 CRM_Core_Resources::singleton()->addVars('coreForm', array('contact_id' => (int) $userID));
2036 }
f03d4901 2037 return is_numeric($userID) ? $userID : NULL;
da8d9879 2038 }
596bff78 2039
3bdf1f3a 2040 /**
2041 * Get the contact id that the form is being submitted for.
2042 *
2043 * @return int|NULL
2044 */
8d388047 2045 public function getContactID() {
2046 return $this->setContactID();
2047 }
2048
f9f40af3 2049 /**
fe482240 2050 * Get the contact id of the logged in user.
f9f40af3 2051 */
00be9182 2052 public function getLoggedInUserContactID() {
596bff78 2053 // check if the user is logged in and has a contact ID
2054 $session = CRM_Core_Session::singleton();
2055 return $session->get('userID');
2056 }
2057
2058 /**
100fef9d 2059 * Add autoselector field -if user has permission to view contacts
596bff78 2060 * If adding this to a form you also need to add to the tpl e.g
2061 *
2062 * {if !empty($selectable)}
2063 * <div class="crm-summary-row">
2064 * <div class="crm-label">{$form.select_contact.label}</div>
2065 * <div class="crm-content">
2066 * {$form.select_contact.html}
2067 * </div>
2068 * </div>
2069 * {/if}
77b97be7 2070 *
6a0b768e
TO
2071 * @param array $profiles
2072 * Ids of profiles that are on the form (to be autofilled).
77b97be7
EM
2073 * @param array $autoCompleteField
2074 *
16b10e64
CW
2075 * - name_field
2076 * - id_field
2077 * - url (for ajax lookup)
596bff78 2078 *
77b97be7 2079 * @todo add data attributes so we can deal with multiple instances on a form
596bff78 2080 */
00be9182 2081 public function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
596bff78 2082 $autoCompleteField = array_merge(array(
353ffa53
TO
2083 'id_field' => 'select_contact_id',
2084 'placeholder' => ts('Select someone else ...'),
2085 'show_hide' => TRUE,
2086 'api' => array('params' => array('contact_type' => 'Individual')),
2087 ), $autoCompleteField);
596bff78 2088
22e263ad 2089 if ($this->canUseAjaxContactLookups()) {
25977d86 2090 $this->assign('selectable', $autoCompleteField['id_field']);
353ffa53
TO
2091 $this->addEntityRef($autoCompleteField['id_field'], NULL, array(
2092 'placeholder' => $autoCompleteField['placeholder'],
af9b09df 2093 'api' => $autoCompleteField['api'],
353ffa53 2094 ));
596bff78 2095
96ed17aa 2096 CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js', 1, 'html-header')
353ffa53
TO
2097 ->addSetting(array(
2098 'form' => array('autocompletes' => $autoCompleteField),
2099 'ids' => array('profile' => $profiles),
2100 ));
596bff78 2101 }
2102 }
2103
dc677c00 2104 /**
dc677c00 2105 */
00be9182 2106 public function canUseAjaxContactLookups() {
dc677c00 2107 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
353ffa53
TO
2108 CRM_Core_Permission::check(array(array('access AJAX API', 'access CiviCRM')))
2109 ) {
f9f40af3
TO
2110 return TRUE;
2111 }
dc677c00
EM
2112 }
2113
596bff78 2114 /**
2115 * Add the options appropriate to cid = zero - ie. autocomplete
2116 *
2117 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
2118 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
2119 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
2120 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
7a9ab499
EM
2121 *
2122 * @param $onlinePaymentProcessorEnabled
596bff78 2123 */
00be9182 2124 public function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
596bff78 2125 $this->assign('nocid', TRUE);
2126 $profiles = array();
22e263ad 2127 if ($this->_values['custom_pre_id']) {
596bff78 2128 $profiles[] = $this->_values['custom_pre_id'];
2129 }
22e263ad 2130 if ($this->_values['custom_post_id']) {
cc57909a 2131 $profiles = array_merge($profiles, (array) $this->_values['custom_post_id']);
596bff78 2132 }
22e263ad 2133 if ($onlinePaymentProcessorEnabled) {
596bff78 2134 $profiles[] = 'billing';
2135 }
22e263ad 2136 if (!empty($this->_values)) {
596bff78 2137 $this->addAutoSelector($profiles);
2138 }
2139 }
9d665938 2140
2141 /**
2142 * Set default values on form for given contact (or no contact defaults)
77b97be7 2143 *
6a0b768e
TO
2144 * @param mixed $profile_id
2145 * (can be id, or profile name).
2146 * @param int $contactID
77b97be7
EM
2147 *
2148 * @return array
9d665938 2149 */
00be9182 2150 public function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
92e4c2a5 2151 try {
9d665938 2152 $defaults = civicrm_api3('profile', 'getsingle', array(
2153 'profile_id' => (array) $profile_id,
2154 'contact_id' => $contactID,
2155 ));
2156 return $defaults;
2157 }
2158 catch (Exception $e) {
9d665938 2159 // the try catch block gives us silent failure -not 100% sure this is a good idea
2160 // as silent failures are often worse than noisy ones
2ab5ff1d 2161 return array();
9d665938 2162 }
2163 }
cae80d9f
CW
2164
2165 /**
fe482240 2166 * Sets form attribute.
cae80d9f
CW
2167 * @see CRM.loadForm
2168 */
00be9182 2169 public function preventAjaxSubmit() {
cae80d9f
CW
2170 $this->setAttribute('data-no-ajax-submit', 'true');
2171 }
2172
2173 /**
fe482240 2174 * Sets form attribute.
cae80d9f
CW
2175 * @see CRM.loadForm
2176 */
00be9182 2177 public function allowAjaxSubmit() {
cae80d9f
CW
2178 $this->removeAttribute('data-no-ajax-submit');
2179 }
e2046b33
CW
2180
2181 /**
fe482240 2182 * Sets page title based on entity and action.
e2046b33
CW
2183 * @param string $entityLabel
2184 */
00be9182 2185 public function setPageTitle($entityLabel) {
e2046b33
CW
2186 switch ($this->_action) {
2187 case CRM_Core_Action::ADD:
2188 CRM_Utils_System::setTitle(ts('New %1', array(1 => $entityLabel)));
2189 break;
f9f40af3 2190
e2046b33
CW
2191 case CRM_Core_Action::UPDATE:
2192 CRM_Utils_System::setTitle(ts('Edit %1', array(1 => $entityLabel)));
2193 break;
f9f40af3 2194
e2046b33
CW
2195 case CRM_Core_Action::VIEW:
2196 case CRM_Core_Action::PREVIEW:
2197 CRM_Utils_System::setTitle(ts('View %1', array(1 => $entityLabel)));
2198 break;
f9f40af3 2199
e2046b33
CW
2200 case CRM_Core_Action::DELETE:
2201 CRM_Utils_System::setTitle(ts('Delete %1', array(1 => $entityLabel)));
2202 break;
2203 }
2204 }
1d07e7ab
CW
2205
2206 /**
2207 * Create a chain-select target field. All settings are optional; the defaults usually work.
2208 *
2209 * @param string $elementName
2210 * @param array $settings
2211 *
2212 * @return HTML_QuickForm_Element
2213 */
2214 public function addChainSelect($elementName, $settings = array()) {
2215 $props = $settings += array(
353ffa53
TO
2216 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array(
2217 'country',
2218 'Country',
2219 'state_province',
af9b09df 2220 'StateProvince',
353ffa53 2221 ), $elementName),
1d07e7ab 2222 'data-callback' => strpos($elementName, 'rovince') ? 'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
757069de 2223 'label' => strpos($elementName, 'rovince') ? ts('State/Province') : ts('County'),
1d07e7ab
CW
2224 'data-empty-prompt' => strpos($elementName, 'rovince') ? ts('Choose country first') : ts('Choose state first'),
2225 'data-none-prompt' => ts('- N/A -'),
2226 'multiple' => FALSE,
2227 'required' => FALSE,
2228 'placeholder' => empty($settings['required']) ? ts('- none -') : ts('- select -'),
2229 );
b248d52b 2230 CRM_Utils_Array::remove($props, 'label', 'required', 'control_field', 'context');
8f9c3cbe 2231 $props['class'] = (empty($props['class']) ? '' : "{$props['class']} ") . 'crm-select2';
1d07e7ab
CW
2232 $props['data-select-prompt'] = $props['placeholder'];
2233 $props['data-name'] = $elementName;
2234
2235 $this->_chainSelectFields[$settings['control_field']] = $elementName;
2236
6a6ab43a
CW
2237 // Passing NULL instead of an array of options
2238 // CRM-15225 - normally QF will reject any selected values that are not part of the field's options, but due to a
2239 // quirk in our patched version of HTML_QuickForm_select, this doesn't happen if the options are NULL
2240 // which seems a bit dirty but it allows our dynamically-popuplated select element to function as expected.
c46f87cf 2241 return $this->add('select', $elementName, $settings['label'], NULL, $settings['required'], $props);
1d07e7ab
CW
2242 }
2243
87ecd5b7 2244 /**
2245 * Add actions menu to results form.
2246 *
c794f667 2247 * @param array $tasks
87ecd5b7 2248 */
2249 public function addTaskMenu($tasks) {
2250 if (is_array($tasks) && !empty($tasks)) {
1a7356e7 2251 // Set constants means this will always load with an empty value, not reloading any submitted value.
2252 // This is appropriate as it is a pseudofield.
2253 $this->setConstants(array('task' => ''));
44543184 2254 $this->assign('taskMetaData', $tasks);
2255 $select = $this->add('select', 'task', NULL, array('' => ts('Actions')), FALSE, array(
2256 'class' => 'crm-select2 crm-action-menu fa-check-circle-o huge crm-search-result-actions')
2257 );
2258 foreach ($tasks as $key => $task) {
2259 $attributes = array();
1a7356e7 2260 if (isset($task['data'])) {
2261 foreach ($task['data'] as $dataKey => $dataValue) {
2262 $attributes['data-' . $dataKey] = $dataValue;
2263 }
44543184 2264 }
2265 $select->addOption($task['title'], $key, $attributes);
2266 }
87ecd5b7 2267 if (empty($this->_actionButtonName)) {
2268 $this->_actionButtonName = $this->getButtonName('next', 'action');
2269 }
2270 $this->assign('actionButtonName', $this->_actionButtonName);
2271 $this->add('submit', $this->_actionButtonName, ts('Go'), array('class' => 'hiddenElement crm-search-go-button'));
2272
2273 // Radio to choose "All items" or "Selected items only"
2274 $selectedRowsRadio = $this->addElement('radio', 'radio_ts', NULL, '', 'ts_sel', array('checked' => 'checked'));
2275 $allRowsRadio = $this->addElement('radio', 'radio_ts', NULL, '', 'ts_all');
2276 $this->assign('ts_sel_id', $selectedRowsRadio->_attributes['id']);
2277 $this->assign('ts_all_id', $allRowsRadio->_attributes['id']);
2278
2279 CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/crm.searchForm.js', 1, 'html-header');
2280 }
2281 }
2282
1d07e7ab
CW
2283 /**
2284 * Set options and attributes for chain select fields based on the controlling field's value
2285 */
2286 private function preProcessChainSelectFields() {
2287 foreach ($this->_chainSelectFields as $control => $target) {
a3984622
OB
2288 // The 'target' might get missing if extensions do removeElement() in a form hook.
2289 if ($this->elementExists($target)) {
2290 $targetField = $this->getElement($target);
2291 $targetType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'county' : 'stateProvince';
2292 $options = array();
2293 // If the control field is on the form, setup chain-select and dynamically populate options
2294 if ($this->elementExists($control)) {
2295 $controlField = $this->getElement($control);
2296 $controlType = $targetType == 'county' ? 'stateProvince' : 'country';
2297
2298 $targetField->setAttribute('class', $targetField->getAttribute('class') . ' crm-chain-select-target');
2299
2300 $css = (string) $controlField->getAttribute('class');
2301 $controlField->updateAttributes(array(
2302 'class' => ($css ? "$css " : 'crm-select2 ') . 'crm-chain-select-control',
2303 'data-target' => $target,
2304 ));
2305 $controlValue = $controlField->getValue();
2306 if ($controlValue) {
2307 $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
2308 if (!$options) {
2309 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
2310 }
4a44fd8a 2311 }
b71cb966 2312 else {
a3984622
OB
2313 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
2314 $targetField->setAttribute('disabled', 'disabled');
8f9c3cbe 2315 }
0db6c3e1 2316 }
a3984622 2317 // Control field not present - fall back to loading default options
0db6c3e1 2318 else {
a3984622 2319 $options = CRM_Core_PseudoConstant::$targetType();
1d07e7ab 2320 }
a3984622
OB
2321 if (!$targetField->getAttribute('multiple')) {
2322 $options = array('' => $targetField->getAttribute('placeholder')) + $options;
2323 $targetField->removeAttribute('placeholder');
2324 }
2325 $targetField->_options = array();
2326 $targetField->loadArray($options);
1d07e7ab 2327 }
1d07e7ab
CW
2328 }
2329 }
bc999cd1
CW
2330
2331 /**
2332 * Validate country / state / county match and suppress unwanted "required" errors
2333 */
2334 private function validateChainSelectFields() {
2335 foreach ($this->_chainSelectFields as $control => $target) {
a3984622 2336 if ($this->elementExists($control) && $this->elementExists($target)) {
f9f40af3 2337 $controlValue = (array) $this->getElementValue($control);
14b2ff15
CW
2338 $targetField = $this->getElement($target);
2339 $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'stateProvince' : 'country';
f9f40af3 2340 $targetValue = array_filter((array) $targetField->getValue());
14b2ff15
CW
2341 if ($targetValue || $this->getElementError($target)) {
2342 $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
2343 if ($targetValue) {
2344 if (!array_intersect($targetValue, array_keys($options))) {
2345 $this->setElementError($target, $controlType == 'country' ? ts('State/Province does not match the selected Country') : ts('County does not match the selected State/Province'));
2346 }
2347 } // Suppress "required" error for field if it has no options
2348 elseif (!$options) {
2349 $this->setElementError($target, NULL);
bc999cd1
CW
2350 }
2351 }
bc999cd1
CW
2352 }
2353 }
2354 }
96025800 2355
0b50eca0 2356 /**
2357 * Assign billing name to the template.
2358 *
2359 * @param array $params
2360 * Form input params, default to $this->_params.
f3f00653 2361 *
2362 * @return string
0b50eca0 2363 */
2364 public function assignBillingName($params = array()) {
2365 $name = '';
2366 if (empty($params)) {
2367 $params = $this->_params;
2368 }
2369 if (!empty($params['billing_first_name'])) {
2370 $name = $params['billing_first_name'];
2371 }
2372
2373 if (!empty($params['billing_middle_name'])) {
2374 $name .= " {$params['billing_middle_name']}";
2375 }
2376
2377 if (!empty($params['billing_last_name'])) {
2378 $name .= " {$params['billing_last_name']}";
2379 }
2380 $name = trim($name);
2381 $this->assign('billingName', $name);
2382 return $name;
2383 }
2384
fd0770bc 2385 /**
2386 * Get the currency for the form.
2387 *
2388 * @todo this should be overriden on the forms rather than having this
2389 * historic, possible handling in here. As we clean that up we should
2390 * add deprecation notices into here.
e9bb043a 2391 *
2392 * @param array $submittedValues
2393 * Array allowed so forms inheriting this class do not break.
2394 * Ideally we would make a clear standard around how submitted values
2395 * are stored (is $this->_values consistently doing that?).
2396 *
2397 * @return string
fd0770bc 2398 */
e9bb043a 2399 public function getCurrency($submittedValues = array()) {
fd0770bc 2400 $currency = CRM_Utils_Array::value('currency', $this->_values);
2401 // For event forms, currency is in a different spot
2402 if (empty($currency)) {
2403 $currency = CRM_Utils_Array::value('currency', CRM_Utils_Array::value('event', $this->_values));
2404 }
2405 if (empty($currency)) {
2406 $currency = CRM_Utils_Request::retrieveValue('currency', 'String');
2407 }
2408 // @todo If empty there is a problem - we should probably put in a deprecation notice
2409 // to warn if that seems to be happening.
2410 return $currency;
2411 }
2412
240b0e65 2413 /**
2414 * Is the form in view or edit mode.
2415 *
2416 * The 'addField' function relies on the form action being one of a set list
2417 * of actions. Checking for these allows for an early return.
2418 *
2419 * @return bool
2420 */
2421 protected function isFormInViewOrEditMode() {
2422 return in_array($this->_action, [
2423 CRM_Core_Action::UPDATE,
2424 CRM_Core_Action::ADD,
2425 CRM_Core_Action::VIEW,
2426 CRM_Core_Action::BROWSE,
2427 CRM_Core_Action::BASIC,
2428 CRM_Core_Action::ADVANCED,
2429 CRM_Core_Action::PREVIEW,
2430 ]);
2431 }
2432
6a488035 2433}