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