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));
1000 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
1001 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1006 * Adds a select based on field metadata
1007 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
1008 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
1010 * Field name to go on the form.
1011 * @param array $props
1012 * Mix of html attributes and special properties, namely.
1013 * - entity (api entity name, can usually be inferred automatically from the form class)
1014 * - field (field name - only needed if different from name used on the form)
1015 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
1016 * - placeholder - set to NULL to disable
1018 * - context - @see CRM_Core_DAO::buildOptionsContext
1019 * @param bool $required
1020 * @throws CRM_Core_Exception
1021 * @return HTML_QuickForm_Element
1023 public function addSelect($name, $props = array(), $required = FALSE) {
1024 if (!isset($props['entity'])) {
1025 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
1027 if (!isset($props['field'])) {
1028 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
1030 // Fetch options from the api unless passed explicitly
1031 if (isset($props['options'])) {
1032 $options = $props['options'];
1035 $info = civicrm_api3($props['entity'], 'getoptions', $props);
1036 $options = $info['values'];
1038 if (!array_key_exists('placeholder', $props)) {
1039 $props['placeholder'] = $required ?
ts('- select -') : CRM_Utils_Array
::value('context', $props) == 'search' ?
ts('- any -') : ts('- none -');
1041 // Handle custom field
1042 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1043 list(, $id) = explode('_', $name);
1044 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
1045 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
1046 if (CRM_Utils_Array
::value('context', $props) != 'search') {
1047 $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);
1052 $info = civicrm_api3($props['entity'], 'getfields');
1053 foreach($info['values'] as $uniqueName => $fieldSpec) {
1055 $uniqueName === $props['field'] ||
1056 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
1057 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
1062 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
1063 if (CRM_Utils_Array
::value('context', $props) != 'search') {
1064 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
1067 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
1068 $props['data-api-entity'] = $props['entity'];
1069 $props['data-api-field'] = $props['field'];
1070 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url', 'options', 'context');
1071 return $this->add('select', $name, $label, $options, $required, $props);
1075 * Add a widget for selecting/editing/creating/copying a profile form
1077 * @param string $name
1078 * HTML form-element name.
1079 * @param string $label
1081 * @param string $allowCoreTypes
1082 * Only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'.
1083 * @param string $allowSubTypes
1084 * Only present a UFGroup if its group_type is compatible with $allowSubypes.
1085 * @param array $entities
1086 * @param bool $default
1089 public function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities, $default = FALSE) {
1091 // FIXME: Instead of adhoc serialization, use a single json_encode()
1092 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
1093 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1094 $this->add('text', $name, $label, array(
1095 'class' => 'crm-profile-selector',
1096 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1097 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1098 'data-entities' => json_encode($entities),
1100 'data-default' => $default,
1105 * @param string $name
1107 * @param $attributes
1108 * @param bool $forceTextarea
1110 public function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1111 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1112 // 2. Based on the option, initialise proper editor
1113 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1116 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1117 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1119 if (!$editor ||
$forceTextarea) {
1120 $editor = 'textarea';
1122 if ($editor == 'joomla default editor') {
1123 $editor = 'joomlaeditor';
1126 if ($editor == 'drupal default editor') {
1127 $editor = 'drupalwysiwyg';
1130 //lets add the editor as a attribute
1131 $attributes['editor'] = $editor;
1133 $this->addElement($editor, $name, $label, $attributes);
1134 $this->assign('editor', $editor);
1136 // include wysiwyg editor js files
1137 // FIXME: This code does not make any sense
1138 $includeWysiwygEditor = FALSE;
1139 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1140 if (!$includeWysiwygEditor) {
1141 $includeWysiwygEditor = TRUE;
1142 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1145 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1151 * @param null $required
1152 * @param null $extra
1154 public function addCountry($id, $title, $required = NULL, $extra = NULL) {
1155 $this->addElement('select', $id, $title,
1157 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1160 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1165 * @param string $name
1168 * @param $attributes
1169 * @param null $required
1170 * @param null $javascriptMethod
1172 public function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1174 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1177 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1184 public function getRootTitle() {
1191 public function getCompleteTitle() {
1192 return $this->getRootTitle() . $this->getTitle();
1196 * @return CRM_Core_Smarty
1198 public static function &getTemplate() {
1199 return self
::$_template;
1203 * @param $elementName
1205 public function addUploadElement($elementName) {
1206 $uploadNames = $this->get('uploadNames');
1207 if (!$uploadNames) {
1208 $uploadNames = array();
1210 if (is_array($elementName)) {
1211 foreach ($elementName as $name) {
1212 if (!in_array($name, $uploadNames)) {
1213 $uploadNames[] = $name;
1218 if (!in_array($elementName, $uploadNames)) {
1219 $uploadNames[] = $elementName;
1222 $this->set('uploadNames', $uploadNames);
1224 $config = CRM_Core_Config
::singleton();
1225 if (!empty($uploadNames)) {
1226 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1233 public function buttonType() {
1234 $uploadNames = $this->get('uploadNames');
1235 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1236 $this->assign('buttonType', $buttonType);
1245 public function getVar($name) {
1246 return isset($this->$name) ?
$this->$name : NULL;
1253 public function setVar($name, $value) {
1254 $this->$name = $value;
1259 * @param string $name
1260 * Name of the element.
1261 * @param string $label
1262 * Label of the element.
1263 * @param array $attributes
1266 * // if you need time
1267 * $attributes = array(
1268 * 'addTime' => true,
1269 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1271 * @param bool $required
1275 public function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1276 if (!empty($attributes['formatType'])) {
1277 // get actual format
1278 $params = array('name' => $attributes['formatType']);
1281 // cache date information
1283 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1284 if (empty($dateFormat[$key])) {
1285 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1286 $dateFormat[$key] = $values;
1289 $values = $dateFormat[$key];
1292 if ($values['date_format']) {
1293 $attributes['format'] = $values['date_format'];
1296 if (!empty($values['time_format'])) {
1297 $attributes['timeFormat'] = $values['time_format'];
1299 $attributes['startOffset'] = $values['start'];
1300 $attributes['endOffset'] = $values['end'];
1303 $config = CRM_Core_Config
::singleton();
1304 if (empty($attributes['format'])) {
1305 $attributes['format'] = $config->dateInputFormat
;
1308 if (!isset($attributes['startOffset'])) {
1309 $attributes['startOffset'] = 10;
1312 if (!isset($attributes['endOffset'])) {
1313 $attributes['endOffset'] = 10;
1316 $this->add('text', $name, $label, $attributes);
1318 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1320 if (!isset($attributes['timeFormat'])) {
1321 $timeFormat = $config->timeInputFormat
;
1324 $timeFormat = $attributes['timeFormat'];
1327 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1329 $show24Hours = TRUE;
1330 if ($timeFormat == 1) {
1331 $show24Hours = FALSE;
1334 //CRM-6664 -we are having time element name
1335 //in either flat string or an array format.
1336 $elementName = $name . '_time';
1337 if (substr($name, -1) == ']') {
1338 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1341 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1346 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1347 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1348 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1354 * Function that will add date and time
1356 public function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1357 $addTime = array('addTime' => TRUE);
1358 if (is_array($attributes)) {
1359 $attributes = array_merge($attributes, $addTime);
1362 $attributes = $addTime;
1365 $this->addDate($name, $label, $required, $attributes);
1369 * Add a currency and money element to the form
1376 $addCurrency = TRUE,
1377 $currencyName = 'currency',
1378 $defaultCurrency = NULL,
1379 $freezeCurrency = FALSE
1381 $element = $this->add('text', $name, $label, $attributes, $required);
1382 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1385 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1392 * Add currency element to the form
1394 function addCurrency(
1398 $defaultCurrency = NULL,
1399 $freezeCurrency = FALSE
1401 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1402 $options = array('class' => 'crm-select2 eight');
1404 $currencies = array('' => '') +
$currencies;
1405 $options['placeholder'] = ts('- none -');
1407 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1408 if ($freezeCurrency) {
1411 if (!$defaultCurrency) {
1412 $config = CRM_Core_Config
::singleton();
1413 $defaultCurrency = $config->defaultCurrency
;
1415 $this->setDefaults(array($name => $defaultCurrency));
1419 * Create a single or multiple entity ref field
1420 * @param string $name
1421 * @param string $label
1422 * @param array $props
1423 * Mix of html and widget properties, including:.
1424 * - select - params to give to select2 widget
1425 * - entity - defaults to contact
1426 * - create - can the user create a new entity on-the-fly?
1427 * Set to TRUE if entity is contact and you want the default profiles,
1428 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1429 * note that permissions are checked automatically
1430 * - api - array of settings for the getlist api wrapper
1431 * note that it accepts a 'params' setting which will be passed to the underlying api
1432 * - placeholder - string
1434 * - class, etc. - other html properties
1435 * @param bool $required
1437 * @return HTML_QuickForm_Element
1439 public function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1440 require_once "api/api.php";
1441 $config = CRM_Core_Config
::singleton();
1442 // Default properties
1443 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1444 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1445 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1447 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1448 unset($props['create']);
1451 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1453 $defaults = array();
1454 if (!empty($props['multiple'])) {
1455 $defaults['multiple'] = TRUE;
1457 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1459 $this->formatReferenceFieldAttributes($props);
1460 return $this->add('text', $name, $label, $props, $required);
1466 private function formatReferenceFieldAttributes(&$props) {
1467 $props['data-select-params'] = json_encode($props['select']);
1468 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1469 $props['data-api-entity'] = $props['entity'];
1470 if (!empty($props['create'])) {
1471 $props['data-create-links'] = json_encode($props['create']);
1473 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1477 * Convert all date fields within the params to mysql date ready for the
1478 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1479 * and if time is defined it is incorporated
1481 * @param array $params
1482 * Input params from the form.
1484 * @todo it would probably be better to work on $this->_params than a passed array
1485 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1488 public function convertDateFieldsToMySQL(&$params){
1489 foreach ($this->_dateFields
as $fieldName => $specs){
1490 if(!empty($params[$fieldName])){
1491 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1492 CRM_Utils_Date
::processDate(
1493 $params[$fieldName],
1494 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1498 if(isset($specs['default'])){
1499 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1506 * @param $elementName
1508 public function removeFileRequiredRules($elementName) {
1509 $this->_required
= array_diff($this->_required
, array($elementName));
1510 if (isset($this->_rules
[$elementName])) {
1511 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1512 if ($ruleInfo['type'] == 'uploadedfile') {
1513 unset($this->_rules
[$elementName][$index]);
1516 if (empty($this->_rules
[$elementName])) {
1517 unset($this->_rules
[$elementName]);
1523 * Function that can be defined in Form to override or
1524 * perform specific action on cancel action
1527 public function cancelAction() {
1531 * Helper function to verify that required fields have been filled
1532 * Typically called within the scope of a FormRule function
1534 public static function validateMandatoryFields($fields, $values, &$errors) {
1535 foreach ($fields as $name => $fld) {
1536 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1537 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1543 * Get contact if for a form object. Prioritise
1544 * - cid in URL if 0 (on behalf on someoneelse)
1545 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1546 * - logged in user id if it matches the one in the cid in the URL
1547 * - contact id validated from a checksum from a checksum
1548 * - cid from the url if the caller has ACL permission to view
1549 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1551 * @return mixed NULL|integer
1553 public function getContactID() {
1554 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1555 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1556 $tempID = $this->_params
['select_contact_id'];
1558 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1559 // event form stores as an indexed array, contribution form not so much...
1560 $tempID = $this->_params
[0]['select_contact_id'];
1563 // force to ignore the authenticated user
1564 if ($tempID === '0' ||
$tempID === 0) {
1565 // we set the cid on the form so that this will be retained for the Confirm page
1566 // in the multi-page form & prevent us returning the $userID when this is called
1568 // we don't really need to set it when $tempID is set because the params have that stored
1569 $this->set('cid', 0);
1573 $userID = $this->getLoggedInUserContactID();
1575 if ($tempID == $userID) {
1579 //check if this is a checksum authentication
1580 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1581 if ($userChecksum) {
1582 //check for anonymous user.
1583 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1588 // check if user has permission, CRM-12062
1589 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1597 * Get the contact id of the logged in user
1599 public function getLoggedInUserContactID() {
1600 // check if the user is logged in and has a contact ID
1601 $session = CRM_Core_Session
::singleton();
1602 return $session->get('userID');
1606 * Add autoselector field -if user has permission to view contacts
1607 * If adding this to a form you also need to add to the tpl e.g
1609 * {if !empty($selectable)}
1610 * <div class="crm-summary-row">
1611 * <div class="crm-label">{$form.select_contact.label}</div>
1612 * <div class="crm-content">
1613 * {$form.select_contact.html}
1618 * @param array $profiles
1619 * Ids of profiles that are on the form (to be autofilled).
1620 * @param array $autoCompleteField
1624 * - url (for ajax lookup)
1626 * @todo add data attributes so we can deal with multiple instances on a form
1628 public function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1629 $autoCompleteField = array_merge(array(
1630 'id_field' => 'select_contact_id',
1631 'placeholder' => ts('Select someone else ...'),
1632 'show_hide' => TRUE,
1633 'api' => array('params' => array('contact_type' => 'Individual'))
1634 ), $autoCompleteField);
1636 if($this->canUseAjaxContactLookups()) {
1637 $this->assign('selectable', $autoCompleteField['id_field']);
1638 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1640 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js', 1, 'html-header')
1642 'form' => array('autocompletes' => $autoCompleteField),
1643 'ids' => array('profile' => $profiles),
1651 public function canUseAjaxContactLookups() {
1652 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1653 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1659 * Add the options appropriate to cid = zero - ie. autocomplete
1661 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1662 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1663 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1664 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1666 public function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1667 $this->assign('nocid', TRUE);
1668 $profiles = array();
1669 if($this->_values
['custom_pre_id']) {
1670 $profiles[] = $this->_values
['custom_pre_id'];
1672 if($this->_values
['custom_post_id']) {
1673 $profiles = array_merge($profiles, (array) $this->_values
['custom_post_id']);
1675 if($onlinePaymentProcessorEnabled) {
1676 $profiles[] = 'billing';
1678 if(!empty($this->_values
)) {
1679 $this->addAutoSelector($profiles);
1684 * Set default values on form for given contact (or no contact defaults)
1686 * @param mixed $profile_id
1687 * (can be id, or profile name).
1688 * @param int $contactID
1692 public function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1694 $defaults = civicrm_api3('profile', 'getsingle', array(
1695 'profile_id' => (array) $profile_id,
1696 'contact_id' => $contactID,
1700 catch (Exception
$e) {
1701 // the try catch block gives us silent failure -not 100% sure this is a good idea
1702 // as silent failures are often worse than noisy ones
1708 * Sets form attribute
1711 public function preventAjaxSubmit() {
1712 $this->setAttribute('data-no-ajax-submit', 'true');
1716 * Sets form attribute
1719 public function allowAjaxSubmit() {
1720 $this->removeAttribute('data-no-ajax-submit');
1724 * Sets page title based on entity and action
1725 * @param string $entityLabel
1727 public function setPageTitle($entityLabel) {
1728 switch ($this->_action
) {
1729 case CRM_Core_Action
::ADD
:
1730 CRM_Utils_System
::setTitle(ts('New %1', array(1 => $entityLabel)));
1733 case CRM_Core_Action
::UPDATE
:
1734 CRM_Utils_System
::setTitle(ts('Edit %1', array(1 => $entityLabel)));
1737 case CRM_Core_Action
::VIEW
:
1738 case CRM_Core_Action
::PREVIEW
:
1739 CRM_Utils_System
::setTitle(ts('View %1', array(1 => $entityLabel)));
1742 case CRM_Core_Action
::DELETE
:
1743 CRM_Utils_System
::setTitle(ts('Delete %1', array(1 => $entityLabel)));
1749 * Create a chain-select target field. All settings are optional; the defaults usually work.
1751 * @param string $elementName
1752 * @param array $settings
1754 * @return HTML_QuickForm_Element
1756 public function addChainSelect($elementName, $settings = array()) {
1757 $props = $settings +
= array(
1758 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array('country', 'Country', 'state_province', 'StateProvince'), $elementName),
1759 'data-callback' => strpos($elementName, 'rovince') ?
'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
1760 'label' => strpos($elementName, 'rovince') ?
ts('State/Province') : ts('County'),
1761 'data-empty-prompt' => strpos($elementName, 'rovince') ?
ts('Choose country first') : ts('Choose state first'),
1762 'data-none-prompt' => ts('- N/A -'),
1763 'multiple' => FALSE,
1764 'required' => FALSE,
1765 'placeholder' => empty($settings['required']) ?
ts('- none -') : ts('- select -'),
1767 CRM_Utils_Array
::remove($props, 'label', 'required', 'control_field');
1768 $props['class'] = (empty($props['class']) ?
'' : "{$props['class']} ") . 'crm-select2';
1769 $props['data-select-prompt'] = $props['placeholder'];
1770 $props['data-name'] = $elementName;
1772 $this->_chainSelectFields
[$settings['control_field']] = $elementName;
1774 // Passing NULL instead of an array of options
1775 // CRM-15225 - normally QF will reject any selected values that are not part of the field's options, but due to a
1776 // quirk in our patched version of HTML_QuickForm_select, this doesn't happen if the options are NULL
1777 // which seems a bit dirty but it allows our dynamically-popuplated select element to function as expected.
1778 return $this->add('select', $elementName, $settings['label'], NULL, $settings['required'], $props);
1782 * Set options and attributes for chain select fields based on the controlling field's value
1784 private function preProcessChainSelectFields() {
1785 foreach ($this->_chainSelectFields
as $control => $target) {
1786 $targetField = $this->getElement($target);
1787 $targetType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ?
'county' : 'stateProvince';
1789 // If the control field is on the form, setup chain-select and dynamically populate options
1790 if ($this->elementExists($control)) {
1791 $controlField = $this->getElement($control);
1792 $controlType = $targetType == 'county' ?
'stateProvince' : 'country';
1794 $targetField->setAttribute('class', $targetField->getAttribute('class') . ' crm-chain-select-target');
1796 $css = (string) $controlField->getAttribute('class');
1797 $controlField->updateAttributes(array(
1798 'class' => ($css ?
"$css " : 'crm-select2 ') . 'crm-chain-select-control',
1799 'data-target' => $target,
1801 $controlValue = $controlField->getValue();
1802 if ($controlValue) {
1803 $options = CRM_Core_BAO_Location
::getChainSelectValues($controlValue, $controlType, TRUE);
1805 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
1808 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
1809 $targetField->setAttribute('disabled', 'disabled');
1812 // Control field not present - fall back to loading default options
1814 $options = CRM_Core_PseudoConstant
::$targetType();
1816 if (!$targetField->getAttribute('multiple')) {
1817 $options = array('' => $targetField->getAttribute('placeholder')) +
$options;
1818 $targetField->removeAttribute('placeholder');
1820 $targetField->_options
= array();
1821 $targetField->loadArray($options);
1826 * Validate country / state / county match and suppress unwanted "required" errors
1828 private function validateChainSelectFields() {
1829 foreach ($this->_chainSelectFields
as $control => $target) {
1830 if ($this->elementExists($control)) {
1831 $controlValue = (array) $this->getElementValue($control);
1832 $targetField = $this->getElement($target);
1833 $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ?
'stateProvince' : 'country';
1834 $targetValue = array_filter((array) $targetField->getValue());
1835 if ($targetValue ||
$this->getElementError($target)) {
1836 $options = CRM_Core_BAO_Location
::getChainSelectValues($controlValue, $controlType, TRUE);
1838 if (!array_intersect($targetValue, array_keys($options))) {
1839 $this->setElementError($target, $controlType == 'country' ?
ts('State/Province does not match the selected Country') : ts('County does not match the selected State/Province'));
1841 } // Suppress "required" error for field if it has no options
1842 elseif (!$options) {
1843 $this->setElementError($target, NULL);