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