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