3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
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
34 * @copyright CiviCRM LLC (c) 2004-2014
39 require_once 'HTML/QuickForm/Page.php';
44 class CRM_Core_Form
extends HTML_QuickForm_Page
{
47 * The state object that this form belongs to
53 * The name of this form
59 * The title of this form
62 protected $_title = NULL;
65 * The options passed into this form
68 protected $_options = NULL;
71 * The mode of operation for this form
77 * The renderer used for this form
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
89 * e.g on a form declare $_dateFields = array(
90 * 'receive_date' => array('default' => 'now'),
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
96 protected $_dateFields = array();
99 * Cache the smarty template for efficiency reasons
101 * @var CRM_Core_Smarty
103 static protected $_template;
106 * Indicate if this form should warn users of unsaved changes
108 protected $unsavedChangesWarn;
111 * What to return to the client if in ajax mode (snippet=json)
115 public $ajaxResponse = array();
118 * Url path used to reach this page
122 public $urlPath = array();
125 * @var CRM_Core_Controller
130 * Constants for attributes for various form elements
131 * attempt to standardize on the number of variations that we
132 * use of the below form elements
136 const ATTR_SPACING
= ' ';
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
146 const CB_PREFIX
= 'mark_x_', CB_PREFIY
= 'mark_y_', CB_PREFIZ
= 'mark_z_', CB_PREFIX_LEN
= 7;
149 * @internal to keep track of chain-select fields
152 private $_chainSelectFields = array();
155 * Constructor for the basic form page
157 * We should not use QuickForm directly. This class provides a lot
158 * of default convenient functions, rules and buttons
160 * @param object $state
161 * State associated with this form.
162 * @param \const|\enum|int $action The mode the form is operating in (None/Create/View/Update/Delete)
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.
168 * @return \CRM_Core_Form
170 function __construct(
172 $action = CRM_Core_Action
::NONE
,
178 $this->_name
= $name;
181 // CRM-15153 - FIXME this name translates to a DOM id and is not always unique!
182 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
185 $this->HTML_QuickForm_Page($this->_name
, $method);
187 $this->_state
=& $state;
189 $this->_state
->setName($this->_name
);
191 $this->_action
= (int) $action;
193 $this->registerRules();
195 // let the constructor initialize this, should happen only once
196 if (!isset(self
::$_template)) {
197 self
::$_template = CRM_Core_Smarty
::singleton();
199 // Workaround for CRM-15153 - give each form a reasonably unique css class
200 $this->addClass(CRM_Utils_System
::getClassName($this));
202 $this->assign('snippet', CRM_Utils_Array
::value('snippet', $_GET));
205 public static function generateID() {
209 * Add one or more css classes to the form
210 * @param string $className
212 public function addClass($className) {
213 $classes = $this->getAttribute('class');
214 $this->setAttribute('class', ($classes ?
"$classes " : '') . $className);
218 * Register all the standard rules that most forms potentially use
223 public function registerRules() {
224 static $rules = array(
225 'title', 'longTitle', 'variable', 'qfVariable',
226 'phone', 'integer', 'query',
228 'domain', 'numberOfDigit',
229 'date', 'currentDate',
230 'asciiFile', 'htmlFile', 'utf8File',
231 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
232 'xssString', 'fileExists', 'autocomplete', 'validContact',
235 foreach ($rules as $rule) {
236 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
241 * Simple easy to use wrapper around addElement. Deal with
242 * simple validation rules
244 * @param string $type
245 * @param string $name
246 * @param string $label
247 * @param string|array $attributes (options for select elements)
248 * @param bool $required
249 * @param array $extra
250 * (attributes for select elements).
252 * @return HTML_QuickForm_Element could be an error object
255 $type, $name, $label = '',
256 $attributes = '', $required = FALSE, $extra = NULL
258 if ($type == 'select' && is_array($extra)) {
259 // Normalize this property
260 if (!empty($extra['multiple'])) {
261 $extra['multiple'] = 'multiple';
264 unset($extra['multiple']);
266 // Add placeholder option for select
267 if (isset($extra['placeholder'])) {
268 if ($extra['placeholder'] === TRUE) {
269 $extra['placeholder'] = $required ?
ts('- select -') : ts('- none -');
271 if (($extra['placeholder'] ||
$extra['placeholder'] === '') && empty($extra['multiple']) && is_array($attributes) && !isset($attributes[''])) {
272 $attributes = array('' => $extra['placeholder']) +
$attributes;
276 $element = $this->addElement($type, $name, $label, $attributes, $extra);
277 if (HTML_QuickForm
::isError($element)) {
278 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
282 if ($type == 'file') {
283 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
286 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
288 if (HTML_QuickForm
::isError($error)) {
289 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
297 * This function is called before buildForm. Any pre-processing that
298 * needs to be done for buildForm should be done here
300 * This is a virtual function and should be redefined if needed
306 public function preProcess() {
310 * This function is called after the form is validated. Any
311 * processing of form state etc should be done in this function.
312 * Typically all processing associated with a form should be done
313 * here and relevant state should be stored in the session
315 * This is a virtual function and should be redefined if needed
321 public function postProcess() {
325 * This function is just a wrapper, so that we can call all the hook functions
326 * @param bool $allowAjax
327 * FIXME: This feels kind of hackish, ideally we would take the json-related code from this function.
328 * and bury it deeper down in the controller
330 public function mainProcess($allowAjax = TRUE) {
331 $this->postProcess();
332 $this->postProcessHook();
334 // Respond with JSON if in AJAX context (also support legacy value '6')
335 if ($allowAjax && !empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
336 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
337 $this->ajaxResponse
['action'] = $this->_action
;
338 if (isset($this->_id
) ||
isset($this->id
)) {
339 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
341 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
346 * The postProcess hook is typically called by the framework
347 * However in a few cases, the form exits or redirects early in which
348 * case it needs to call this function so other modules can do the needful
349 * Calling this function directly should be avoided if possible. In general a
350 * better way is to do setUserContext so the framework does the redirect
353 public function postProcessHook() {
354 CRM_Utils_Hook
::postProcess(get_class($this), $this);
358 * This virtual function is used to build the form. It replaces the
359 * buildForm associated with QuickForm_Page. This allows us to put
360 * preProcess in front of the actual form building routine
366 public function buildQuickForm() {
370 * This virtual function is used to set the default values of
371 * various form elements
375 * @return array reference to the array of default values
378 public function setDefaultValues() {
382 * This is a virtual function that adds group and global rules to
383 * the form. Keeping it distinct from the form to keep code small
384 * and localized in the form building code
390 public function addRules() {
394 * Performs the server side validation
396 * @return boolean true if no error found
397 * @throws HTML_QuickForm_Error
399 public function validate() {
400 $error = parent
::validate();
402 $this->validateChainSelectFields();
404 $hookErrors = CRM_Utils_Hook
::validate(
406 $this->_submitValues
,
411 if (!is_array($hookErrors)) {
412 $hookErrors = array();
415 CRM_Utils_Hook
::validateForm(
417 $this->_submitValues
,
423 if (!empty($hookErrors)) {
424 $this->_errors +
= $hookErrors;
427 return (0 == count($this->_errors
));
431 * Core function that builds the form. We redefine this function
432 * here and expect all CRM forms to build their form in the function
436 public function buildForm() {
437 $this->_formBuilt
= TRUE;
441 CRM_Utils_Hook
::preProcess(get_class($this), $this);
443 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
446 $this->controller
->_key
&&
447 $this->controller
->_generateQFKey
449 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
450 $this->assign('qfKey', $this->controller
->_key
);
454 // _generateQFKey suppresses the qfKey generation on form snippets that
455 // are part of other forms, hence we use that to avoid adding entryURL
456 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
457 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
460 $this->buildQuickForm();
462 $defaults = $this->setDefaultValues();
463 unset($defaults['qfKey']);
465 if (!empty($defaults)) {
466 $this->setDefaults($defaults);
469 // call the form hook
470 // also call the hook function so any modules can set thier own custom defaults
471 // the user can do both the form and set default values with this hook
472 CRM_Utils_Hook
::buildForm(get_class($this), $this);
476 //Set html data-attribute to enable warning user of unsaved changes
477 if ($this->unsavedChangesWarn
=== TRUE
478 ||
(!isset($this->unsavedChangesWarn
)
479 && ($this->_action
& CRM_Core_Action
::ADD ||
$this->_action
& CRM_Core_Action
::UPDATE
)
482 $this->setAttribute('data-warn-changes', 'true');
487 * Add default Next / Back buttons
489 * @param array array of associative arrays in the order in which the buttons should be
490 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
491 * The base form class will define a bunch of static arrays for commonly used
498 public function addButtons($params) {
501 foreach ($params as $button) {
502 $js = CRM_Utils_Array
::value('js', $button);
503 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
505 $attrs = array('class' => 'crm-form-submit default');
508 $attrs = array('class' => 'crm-form-submit');
512 $attrs = array_merge($js, $attrs);
515 if ($button['type'] === 'cancel') {
516 $attrs['class'] .= ' cancel';
519 if ($button['type'] === 'reset') {
520 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
523 if (!empty($button['subName'])) {
524 $buttonName = $this->getButtonName($button['type'], $button['subName']);
527 $buttonName = $this->getButtonName($button['type']);
530 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
531 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
533 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
535 if (!empty($button['isDefault'])) {
536 $this->setDefaultAction($button['type']);
539 // if button type is upload, set the enctype
540 if ($button['type'] == 'upload') {
541 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
542 $this->setMaxFileSize();
545 // hack - addGroup uses an array to express variable spacing, read from the last element
546 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
548 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
552 * Getter function for Name
556 public function getName() {
561 * Getter function for State
565 public function &getState() {
566 return $this->_state
;
570 * Getter function for StateType
574 public function getStateType() {
575 return $this->_state
->getType();
579 * Getter function for title. Should be over-ridden by derived class
583 public function getTitle() {
584 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
588 * Setter function for title.
590 * @param string $title
591 * The title of the form.
595 public function setTitle($title) {
596 $this->_title
= $title;
600 * Setter function for options
606 public function setOptions($options) {
607 $this->_options
= $options;
611 * Getter function for link.
615 public function getLink() {
616 $config = CRM_Core_Config
::singleton();
617 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
618 '_qf_' . $this->_name
. '_display=true'
623 * Boolean function to determine if this is a one form page
627 public function isSimpleForm() {
628 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
632 * Getter function for Form Action
636 public function getFormAction() {
637 return $this->_attributes
['action'];
641 * Setter function for Form Action
647 public function setFormAction($action) {
648 $this->_attributes
['action'] = $action;
652 * Render form and return contents
656 public function toSmarty() {
657 $this->preProcessChainSelectFields();
658 $renderer = $this->getRenderer();
659 $this->accept($renderer);
660 $content = $renderer->toArray();
661 $content['formName'] = $this->getName();
663 $content['formClass'] = CRM_Utils_System
::getClassName($this);
668 * Getter function for renderer. If renderer is not set
669 * create one and initialize it
673 public function &getRenderer() {
674 if (!isset($this->_renderer
)) {
675 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
677 return $this->_renderer
;
681 * Use the form name to create the tpl file name
685 public function getTemplateFileName() {
686 $ext = CRM_Extension_System
::singleton()->getMapper();
687 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
688 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
689 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
692 $tplname = str_replace('_',
694 CRM_Utils_System
::getClassName($this)
701 * A wrapper for getTemplateFileName that includes calling the hook to
702 * prevent us from having to copy & paste the logic of calling the hook
704 public function getHookedTemplateFileName() {
705 $pageTemplateFile = $this->getTemplateFileName();
706 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
707 return $pageTemplateFile;
711 * Default extra tpl file basically just replaces .tpl with .extra.tpl
712 * i.e. we dont override
716 public function overrideExtraTemplateFileName() {
721 * Error reporting mechanism
723 * @param string $message
727 * @param CRM_Core_DAO $dao
728 * A data access object on which we perform a rollback if non - empty.
732 public function error($message, $code = NULL, $dao = NULL) {
734 $dao->query('ROLLBACK');
737 $error = CRM_Core_Error
::singleton();
739 $error->push($code, $message);
743 * Store the variable with the value in the form scope
745 * @param string name : name of the variable
746 * @param mixed value : value of the variable
752 public function set($name, $value) {
753 $this->controller
->set($name, $value);
757 * Get the variable from the form scope
759 * @param string name : name of the variable
765 public function get($name) {
766 return $this->controller
->get($name);
774 public function getAction() {
775 return $this->_action
;
782 * The mode we want to set the form.
786 public function setAction($action) {
787 $this->_action
= $action;
791 * Assign value to name in template
795 * @param mixed $value
800 public function assign($var, $value = NULL) {
801 self
::$_template->assign($var, $value);
805 * Assign value to name in template by reference
809 * @param mixed $value
814 public function assign_by_ref($var, &$value) {
815 self
::$_template->assign_by_ref($var, $value);
819 * Appends values to template variables
821 * @param array|string $tpl_var the template variable name(s)
822 * @param mixed $value
823 * The value to append.
826 public function append($tpl_var, $value = NULL, $merge = FALSE) {
827 self
::$_template->append($tpl_var, $value, $merge);
831 * Returns an array containing template variables
833 * @param string $name
837 public function get_template_vars($name = NULL) {
838 return self
::$_template->get_template_vars($name);
842 * @param string $name
845 * @param array $attributes
846 * @param null $separator
847 * @param bool $required
849 * @return HTML_QuickForm_group
851 public function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
853 $attributes = $attributes ?
$attributes : array();
854 $allowClear = !empty($attributes['allowClear']);
855 unset($attributes['allowClear']);
856 $attributes +
= array('id_suffix' => $name);
857 foreach ($values as $key => $var) {
858 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
860 $group = $this->addGroup($options, $name, $title, $separator);
862 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
865 $group->setAttribute('allowClear', TRUE);
873 * @param bool $allowClear
874 * @param null $required
875 * @param array $attributes
877 public function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
878 $attributes +
= array('id_suffix' => $id);
880 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
881 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
883 $group = $this->addGroup($choice, $id, $title);
885 $group->setAttribute('allowClear', TRUE);
888 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
897 * @param null $attributes
898 * @param null $required
899 * @param null $javascriptMethod
900 * @param string $separator
901 * @param bool $flipValues
903 function addCheckBox(
904 $id, $title, $values, $other = NULL,
905 $attributes = NULL, $required = NULL,
906 $javascriptMethod = NULL,
907 $separator = '<br />', $flipValues = FALSE
911 if ($javascriptMethod) {
912 foreach ($values as $key => $var) {
914 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
917 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
922 foreach ($values as $key => $var) {
924 $options[] = $this->createElement('checkbox', $var, NULL, $key);
927 $options[] = $this->createElement('checkbox', $key, NULL, $var);
932 $this->addGroup($options, $id, $title, $separator);
935 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
940 ts('%1 is a required field.', array(1 => $title)),
946 public function resetValues() {
947 $data = $this->controller
->container();
948 $data['values'][$this->_name
] = array();
952 * Simple shell that derived classes can call to add buttons to
953 * the form with a customized title for the main Submit
955 * @param string $title
956 * Title of the main button.
957 * @param string $nextType
958 * Button type for the form after processing.
959 * @param string $backType
960 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
964 public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
966 if ($backType != NULL) {
969 'name' => ts('Previous'),
972 if ($nextType != NULL) {
979 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
981 $buttons[] = $nextButton;
983 $this->addButtons($buttons);
987 * @param string $name
988 * @param string $from
990 * @param string $label
991 * @param string $dateFormat
992 * @param bool $required
993 * @param bool $displayTime
995 public function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
997 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
998 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1001 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
1002 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1007 * Adds a select based on field metadata
1008 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
1009 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
1011 * Field name to go on the form.
1012 * @param array $props
1013 * Mix of html attributes and special properties, namely.
1014 * - entity (api entity name, can usually be inferred automatically from the form class)
1015 * - field (field name - only needed if different from name used on the form)
1016 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
1017 * - placeholder - set to NULL to disable
1019 * - context - @see CRM_Core_DAO::buildOptionsContext
1020 * @param bool $required
1021 * @throws CRM_Core_Exception
1022 * @return HTML_QuickForm_Element
1024 public function addSelect($name, $props = array(), $required = FALSE) {
1025 if (!isset($props['entity'])) {
1026 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
1028 if (!isset($props['field'])) {
1029 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
1031 // Fetch options from the api unless passed explicitly
1032 if (isset($props['options'])) {
1033 $options = $props['options'];
1036 $info = civicrm_api3($props['entity'], 'getoptions', $props);
1037 $options = $info['values'];
1039 if (!array_key_exists('placeholder', $props)) {
1040 $props['placeholder'] = $required ?
ts('- select -') : CRM_Utils_Array
::value('context', $props) == 'search' ?
ts('- any -') : ts('- none -');
1042 // Handle custom field
1043 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1044 list(, $id) = explode('_', $name);
1045 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
1046 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
1047 if (CRM_Utils_Array
::value('context', $props) != 'search') {
1048 $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);
1053 $info = civicrm_api3($props['entity'], 'getfields');
1054 foreach ($info['values'] as $uniqueName => $fieldSpec) {
1056 $uniqueName === $props['field'] ||
1057 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
1058 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
1063 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
1064 if (CRM_Utils_Array
::value('context', $props) != 'search') {
1065 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
1068 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
1069 $props['data-api-entity'] = $props['entity'];
1070 $props['data-api-field'] = $props['field'];
1071 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url', 'options', 'context');
1072 return $this->add('select', $name, $label, $options, $required, $props);
1076 * Add a widget for selecting/editing/creating/copying a profile form
1078 * @param string $name
1079 * HTML form-element name.
1080 * @param string $label
1082 * @param string $allowCoreTypes
1083 * Only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'.
1084 * @param string $allowSubTypes
1085 * Only present a UFGroup if its group_type is compatible with $allowSubypes.
1086 * @param array $entities
1087 * @param bool $default
1090 public function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities, $default = FALSE) {
1092 // FIXME: Instead of adhoc serialization, use a single json_encode()
1093 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
1094 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1095 $this->add('text', $name, $label, array(
1096 'class' => 'crm-profile-selector',
1097 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1098 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1099 'data-entities' => json_encode($entities),
1101 'data-default' => $default,
1106 * @param string $name
1108 * @param $attributes
1109 * @param bool $forceTextarea
1111 public function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1112 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1113 // 2. Based on the option, initialise proper editor
1114 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1117 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1118 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1120 if (!$editor ||
$forceTextarea) {
1121 $editor = 'textarea';
1123 if ($editor == 'joomla default editor') {
1124 $editor = 'joomlaeditor';
1127 if ($editor == 'drupal default editor') {
1128 $editor = 'drupalwysiwyg';
1131 //lets add the editor as a attribute
1132 $attributes['editor'] = $editor;
1134 $this->addElement($editor, $name, $label, $attributes);
1135 $this->assign('editor', $editor);
1137 // include wysiwyg editor js files
1138 // FIXME: This code does not make any sense
1139 $includeWysiwygEditor = FALSE;
1140 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1141 if (!$includeWysiwygEditor) {
1142 $includeWysiwygEditor = TRUE;
1143 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1146 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1152 * @param null $required
1153 * @param null $extra
1155 public function addCountry($id, $title, $required = NULL, $extra = NULL) {
1156 $this->addElement('select', $id, $title,
1158 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1161 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1166 * @param string $name
1169 * @param $attributes
1170 * @param null $required
1171 * @param null $javascriptMethod
1173 public function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1175 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1178 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1185 public function getRootTitle() {
1192 public function getCompleteTitle() {
1193 return $this->getRootTitle() . $this->getTitle();
1197 * @return CRM_Core_Smarty
1199 public static function &getTemplate() {
1200 return self
::$_template;
1204 * @param $elementName
1206 public function addUploadElement($elementName) {
1207 $uploadNames = $this->get('uploadNames');
1208 if (!$uploadNames) {
1209 $uploadNames = array();
1211 if (is_array($elementName)) {
1212 foreach ($elementName as $name) {
1213 if (!in_array($name, $uploadNames)) {
1214 $uploadNames[] = $name;
1219 if (!in_array($elementName, $uploadNames)) {
1220 $uploadNames[] = $elementName;
1223 $this->set('uploadNames', $uploadNames);
1225 $config = CRM_Core_Config
::singleton();
1226 if (!empty($uploadNames)) {
1227 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1234 public function buttonType() {
1235 $uploadNames = $this->get('uploadNames');
1236 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1237 $this->assign('buttonType', $buttonType);
1246 public function getVar($name) {
1247 return isset($this->$name) ?
$this->$name : NULL;
1254 public function setVar($name, $value) {
1255 $this->$name = $value;
1260 * @param string $name
1261 * Name of the element.
1262 * @param string $label
1263 * Label of the element.
1264 * @param array $attributes
1267 * // if you need time
1268 * $attributes = array(
1269 * 'addTime' => true,
1270 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1272 * @param bool $required
1276 public function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1277 if (!empty($attributes['formatType'])) {
1278 // get actual format
1279 $params = array('name' => $attributes['formatType']);
1282 // cache date information
1284 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1285 if (empty($dateFormat[$key])) {
1286 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1287 $dateFormat[$key] = $values;
1290 $values = $dateFormat[$key];
1293 if ($values['date_format']) {
1294 $attributes['format'] = $values['date_format'];
1297 if (!empty($values['time_format'])) {
1298 $attributes['timeFormat'] = $values['time_format'];
1300 $attributes['startOffset'] = $values['start'];
1301 $attributes['endOffset'] = $values['end'];
1304 $config = CRM_Core_Config
::singleton();
1305 if (empty($attributes['format'])) {
1306 $attributes['format'] = $config->dateInputFormat
;
1309 if (!isset($attributes['startOffset'])) {
1310 $attributes['startOffset'] = 10;
1313 if (!isset($attributes['endOffset'])) {
1314 $attributes['endOffset'] = 10;
1317 $this->add('text', $name, $label, $attributes);
1319 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1321 if (!isset($attributes['timeFormat'])) {
1322 $timeFormat = $config->timeInputFormat
;
1325 $timeFormat = $attributes['timeFormat'];
1328 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1330 $show24Hours = TRUE;
1331 if ($timeFormat == 1) {
1332 $show24Hours = FALSE;
1335 //CRM-6664 -we are having time element name
1336 //in either flat string or an array format.
1337 $elementName = $name . '_time';
1338 if (substr($name, -1) == ']') {
1339 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1342 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1347 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1348 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1349 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1355 * Function that will add date and time
1357 public function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1358 $addTime = array('addTime' => TRUE);
1359 if (is_array($attributes)) {
1360 $attributes = array_merge($attributes, $addTime);
1363 $attributes = $addTime;
1366 $this->addDate($name, $label, $required, $attributes);
1370 * Add a currency and money element to the form
1377 $addCurrency = TRUE,
1378 $currencyName = 'currency',
1379 $defaultCurrency = NULL,
1380 $freezeCurrency = FALSE
1382 $element = $this->add('text', $name, $label, $attributes, $required);
1383 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1386 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1393 * Add currency element to the form
1395 function addCurrency(
1399 $defaultCurrency = NULL,
1400 $freezeCurrency = FALSE
1402 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1403 $options = array('class' => 'crm-select2 eight');
1405 $currencies = array('' => '') +
$currencies;
1406 $options['placeholder'] = ts('- none -');
1408 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1409 if ($freezeCurrency) {
1412 if (!$defaultCurrency) {
1413 $config = CRM_Core_Config
::singleton();
1414 $defaultCurrency = $config->defaultCurrency
;
1416 $this->setDefaults(array($name => $defaultCurrency));
1420 * Create a single or multiple entity ref field
1421 * @param string $name
1422 * @param string $label
1423 * @param array $props
1424 * Mix of html and widget properties, including:.
1425 * - select - params to give to select2 widget
1426 * - entity - defaults to contact
1427 * - create - can the user create a new entity on-the-fly?
1428 * Set to TRUE if entity is contact and you want the default profiles,
1429 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1430 * note that permissions are checked automatically
1431 * - api - array of settings for the getlist api wrapper
1432 * note that it accepts a 'params' setting which will be passed to the underlying api
1433 * - placeholder - string
1435 * - class, etc. - other html properties
1436 * @param bool $required
1438 * @return HTML_QuickForm_Element
1440 public function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1441 require_once "api/api.php";
1442 $config = CRM_Core_Config
::singleton();
1443 // Default properties
1444 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1445 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1446 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1448 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1449 unset($props['create']);
1452 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1454 $defaults = array();
1455 if (!empty($props['multiple'])) {
1456 $defaults['multiple'] = TRUE;
1458 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1460 $this->formatReferenceFieldAttributes($props);
1461 return $this->add('text', $name, $label, $props, $required);
1467 private function formatReferenceFieldAttributes(&$props) {
1468 $props['data-select-params'] = json_encode($props['select']);
1469 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1470 $props['data-api-entity'] = $props['entity'];
1471 if (!empty($props['create'])) {
1472 $props['data-create-links'] = json_encode($props['create']);
1474 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1478 * Convert all date fields within the params to mysql date ready for the
1479 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1480 * and if time is defined it is incorporated
1482 * @param array $params
1483 * Input params from the form.
1485 * @todo it would probably be better to work on $this->_params than a passed array
1486 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1489 public function convertDateFieldsToMySQL(&$params) {
1490 foreach ($this->_dateFields
as $fieldName => $specs) {
1491 if (!empty($params[$fieldName])) {
1492 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1493 CRM_Utils_Date
::processDate(
1494 $params[$fieldName],
1495 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1499 if (isset($specs['default'])) {
1500 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1507 * @param $elementName
1509 public function removeFileRequiredRules($elementName) {
1510 $this->_required
= array_diff($this->_required
, array($elementName));
1511 if (isset($this->_rules
[$elementName])) {
1512 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1513 if ($ruleInfo['type'] == 'uploadedfile') {
1514 unset($this->_rules
[$elementName][$index]);
1517 if (empty($this->_rules
[$elementName])) {
1518 unset($this->_rules
[$elementName]);
1524 * Function that can be defined in Form to override or
1525 * perform specific action on cancel action
1528 public function cancelAction() {
1532 * Helper function to verify that required fields have been filled
1533 * Typically called within the scope of a FormRule function
1535 public static function validateMandatoryFields($fields, $values, &$errors) {
1536 foreach ($fields as $name => $fld) {
1537 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1538 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1544 * Get contact if for a form object. Prioritise
1545 * - cid in URL if 0 (on behalf on someoneelse)
1546 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1547 * - logged in user id if it matches the one in the cid in the URL
1548 * - contact id validated from a checksum from a checksum
1549 * - cid from the url if the caller has ACL permission to view
1550 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1552 * @return mixed NULL|integer
1554 public function getContactID() {
1555 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1556 if (isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1557 $tempID = $this->_params
['select_contact_id'];
1559 if (isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1560 // event form stores as an indexed array, contribution form not so much...
1561 $tempID = $this->_params
[0]['select_contact_id'];
1564 // force to ignore the authenticated user
1565 if ($tempID === '0' ||
$tempID === 0) {
1566 // we set the cid on the form so that this will be retained for the Confirm page
1567 // in the multi-page form & prevent us returning the $userID when this is called
1569 // we don't really need to set it when $tempID is set because the params have that stored
1570 $this->set('cid', 0);
1574 $userID = $this->getLoggedInUserContactID();
1576 if ($tempID == $userID) {
1580 //check if this is a checksum authentication
1581 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1582 if ($userChecksum) {
1583 //check for anonymous user.
1584 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1589 // check if user has permission, CRM-12062
1590 elseif ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1598 * Get the contact id of the logged in user
1600 public function getLoggedInUserContactID() {
1601 // check if the user is logged in and has a contact ID
1602 $session = CRM_Core_Session
::singleton();
1603 return $session->get('userID');
1607 * Add autoselector field -if user has permission to view contacts
1608 * If adding this to a form you also need to add to the tpl e.g
1610 * {if !empty($selectable)}
1611 * <div class="crm-summary-row">
1612 * <div class="crm-label">{$form.select_contact.label}</div>
1613 * <div class="crm-content">
1614 * {$form.select_contact.html}
1619 * @param array $profiles
1620 * Ids of profiles that are on the form (to be autofilled).
1621 * @param array $autoCompleteField
1625 * - url (for ajax lookup)
1627 * @todo add data attributes so we can deal with multiple instances on a form
1629 public function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1630 $autoCompleteField = array_merge(array(
1631 'id_field' => 'select_contact_id',
1632 'placeholder' => ts('Select someone else ...'),
1633 'show_hide' => TRUE,
1634 'api' => array('params' => array('contact_type' => 'Individual'))
1635 ), $autoCompleteField);
1637 if ($this->canUseAjaxContactLookups()) {
1638 $this->assign('selectable', $autoCompleteField['id_field']);
1639 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1641 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js', 1, 'html-header')
1643 'form' => array('autocompletes' => $autoCompleteField),
1644 'ids' => array('profile' => $profiles),
1652 public function canUseAjaxContactLookups() {
1653 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1654 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1660 * Add the options appropriate to cid = zero - ie. autocomplete
1662 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1663 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1664 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1665 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1667 public function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1668 $this->assign('nocid', TRUE);
1669 $profiles = array();
1670 if ($this->_values
['custom_pre_id']) {
1671 $profiles[] = $this->_values
['custom_pre_id'];
1673 if ($this->_values
['custom_post_id']) {
1674 $profiles = array_merge($profiles, (array) $this->_values
['custom_post_id']);
1676 if ($onlinePaymentProcessorEnabled) {
1677 $profiles[] = 'billing';
1679 if (!empty($this->_values
)) {
1680 $this->addAutoSelector($profiles);
1685 * Set default values on form for given contact (or no contact defaults)
1687 * @param mixed $profile_id
1688 * (can be id, or profile name).
1689 * @param int $contactID
1693 public function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1695 $defaults = civicrm_api3('profile', 'getsingle', array(
1696 'profile_id' => (array) $profile_id,
1697 'contact_id' => $contactID,
1701 catch (Exception
$e) {
1702 // the try catch block gives us silent failure -not 100% sure this is a good idea
1703 // as silent failures are often worse than noisy ones
1709 * Sets form attribute
1712 public function preventAjaxSubmit() {
1713 $this->setAttribute('data-no-ajax-submit', 'true');
1717 * Sets form attribute
1720 public function allowAjaxSubmit() {
1721 $this->removeAttribute('data-no-ajax-submit');
1725 * Sets page title based on entity and action
1726 * @param string $entityLabel
1728 public function setPageTitle($entityLabel) {
1729 switch ($this->_action
) {
1730 case CRM_Core_Action
::ADD
:
1731 CRM_Utils_System
::setTitle(ts('New %1', array(1 => $entityLabel)));
1734 case CRM_Core_Action
::UPDATE
:
1735 CRM_Utils_System
::setTitle(ts('Edit %1', array(1 => $entityLabel)));
1738 case CRM_Core_Action
::VIEW
:
1739 case CRM_Core_Action
::PREVIEW
:
1740 CRM_Utils_System
::setTitle(ts('View %1', array(1 => $entityLabel)));
1743 case CRM_Core_Action
::DELETE
:
1744 CRM_Utils_System
::setTitle(ts('Delete %1', array(1 => $entityLabel)));
1750 * Create a chain-select target field. All settings are optional; the defaults usually work.
1752 * @param string $elementName
1753 * @param array $settings
1755 * @return HTML_QuickForm_Element
1757 public function addChainSelect($elementName, $settings = array()) {
1758 $props = $settings +
= array(
1759 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array('country', 'Country', 'state_province', 'StateProvince'), $elementName),
1760 'data-callback' => strpos($elementName, 'rovince') ?
'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
1761 'label' => strpos($elementName, 'rovince') ?
ts('State/Province') : ts('County'),
1762 'data-empty-prompt' => strpos($elementName, 'rovince') ?
ts('Choose country first') : ts('Choose state first'),
1763 'data-none-prompt' => ts('- N/A -'),
1764 'multiple' => FALSE,
1765 'required' => FALSE,
1766 'placeholder' => empty($settings['required']) ?
ts('- none -') : ts('- select -'),
1768 CRM_Utils_Array
::remove($props, 'label', 'required', 'control_field');
1769 $props['class'] = (empty($props['class']) ?
'' : "{$props['class']} ") . 'crm-select2';
1770 $props['data-select-prompt'] = $props['placeholder'];
1771 $props['data-name'] = $elementName;
1773 $this->_chainSelectFields
[$settings['control_field']] = $elementName;
1775 // Passing NULL instead of an array of options
1776 // CRM-15225 - normally QF will reject any selected values that are not part of the field's options, but due to a
1777 // quirk in our patched version of HTML_QuickForm_select, this doesn't happen if the options are NULL
1778 // which seems a bit dirty but it allows our dynamically-popuplated select element to function as expected.
1779 return $this->add('select', $elementName, $settings['label'], NULL, $settings['required'], $props);
1783 * Set options and attributes for chain select fields based on the controlling field's value
1785 private function preProcessChainSelectFields() {
1786 foreach ($this->_chainSelectFields
as $control => $target) {
1787 $targetField = $this->getElement($target);
1788 $targetType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ?
'county' : 'stateProvince';
1790 // If the control field is on the form, setup chain-select and dynamically populate options
1791 if ($this->elementExists($control)) {
1792 $controlField = $this->getElement($control);
1793 $controlType = $targetType == 'county' ?
'stateProvince' : 'country';
1795 $targetField->setAttribute('class', $targetField->getAttribute('class') . ' crm-chain-select-target');
1797 $css = (string) $controlField->getAttribute('class');
1798 $controlField->updateAttributes(array(
1799 'class' => ($css ?
"$css " : 'crm-select2 ') . 'crm-chain-select-control',
1800 'data-target' => $target,
1802 $controlValue = $controlField->getValue();
1803 if ($controlValue) {
1804 $options = CRM_Core_BAO_Location
::getChainSelectValues($controlValue, $controlType, TRUE);
1806 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
1810 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
1811 $targetField->setAttribute('disabled', 'disabled');
1814 // Control field not present - fall back to loading default options
1816 $options = CRM_Core_PseudoConstant
::$targetType();
1818 if (!$targetField->getAttribute('multiple')) {
1819 $options = array('' => $targetField->getAttribute('placeholder')) +
$options;
1820 $targetField->removeAttribute('placeholder');
1822 $targetField->_options
= array();
1823 $targetField->loadArray($options);
1828 * Validate country / state / county match and suppress unwanted "required" errors
1830 private function validateChainSelectFields() {
1831 foreach ($this->_chainSelectFields
as $control => $target) {
1832 if ($this->elementExists($control)) {
1833 $controlValue = (array) $this->getElementValue($control);
1834 $targetField = $this->getElement($target);
1835 $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ?
'stateProvince' : 'country';
1836 $targetValue = array_filter((array) $targetField->getValue());
1837 if ($targetValue ||
$this->getElementError($target)) {
1838 $options = CRM_Core_BAO_Location
::getChainSelectValues($controlValue, $controlType, TRUE);
1840 if (!array_intersect($targetValue, array_keys($options))) {
1841 $this->setElementError($target, $controlType == 'country' ?
ts('State/Province does not match the selected Country') : ts('County does not match the selected State/Province'));
1843 } // Suppress "required" error for field if it has no options
1844 elseif (!$options) {
1845 $this->setElementError($target, NULL);