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 State associated with this form
161 * @param \const|\enum|int $action The mode the form is operating in (None/Create/View/Update/Delete)
162 * @param string $method The type of http method used (GET/POST)
163 * @param string $name The name of the form if different from class name
165 * @return \CRM_Core_Form
167 function __construct(
169 $action = CRM_Core_Action
::NONE
,
175 $this->_name
= $name;
178 // CRM-15153 - FIXME this name translates to a DOM id and is not always unique!
179 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
182 $this->HTML_QuickForm_Page($this->_name
, $method);
184 $this->_state
=& $state;
186 $this->_state
->setName($this->_name
);
188 $this->_action
= (int) $action;
190 $this->registerRules();
192 // let the constructor initialize this, should happen only once
193 if (!isset(self
::$_template)) {
194 self
::$_template = CRM_Core_Smarty
::singleton();
196 // Workaround for CRM-15153 - give each form a reasonably unique css class
197 $this->addClass(CRM_Utils_System
::getClassName($this));
199 $this->assign('snippet', CRM_Utils_Array
::value('snippet', $_GET));
202 public static function generateID() {
206 * Add one or more css classes to the form
207 * @param string $className
209 public function addClass($className) {
210 $classes = $this->getAttribute('class');
211 $this->setAttribute('class', ($classes ?
"$classes " : '') . $className);
215 * Register all the standard rules that most forms potentially use
220 public function registerRules() {
221 static $rules = array(
222 'title', 'longTitle', 'variable', 'qfVariable',
223 'phone', 'integer', 'query',
225 'domain', 'numberOfDigit',
226 'date', 'currentDate',
227 'asciiFile', 'htmlFile', 'utf8File',
228 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
229 'xssString', 'fileExists', 'autocomplete', 'validContact',
232 foreach ($rules as $rule) {
233 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
238 * Simple easy to use wrapper around addElement. Deal with
239 * simple validation rules
241 * @param string $type
242 * @param string $name
243 * @param string $label
244 * @param string|array $attributes (options for select elements)
245 * @param bool $required
246 * @param array $extra (attributes for select elements)
248 * @return HTML_QuickForm_Element could be an error object
250 function &add($type, $name, $label = '',
251 $attributes = '', $required = FALSE, $extra = NULL
253 if ($type == 'select' && is_array($extra)) {
254 // Normalize this property
255 if (!empty($extra['multiple'])) {
256 $extra['multiple'] = 'multiple';
259 unset($extra['multiple']);
261 // Add placeholder option for select
262 if (isset($extra['placeholder'])) {
263 if ($extra['placeholder'] === TRUE) {
264 $extra['placeholder'] = $required ?
ts('- select -') : ts('- none -');
266 if (($extra['placeholder'] ||
$extra['placeholder'] === '') && empty($extra['multiple']) && is_array($attributes) && !isset($attributes[''])) {
267 $attributes = array('' => $extra['placeholder']) +
$attributes;
271 $element = $this->addElement($type, $name, $label, $attributes, $extra);
272 if (HTML_QuickForm
::isError($element)) {
273 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
277 if ($type == 'file') {
278 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
281 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
283 if (HTML_QuickForm
::isError($error)) {
284 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
292 * This function is called before buildForm. Any pre-processing that
293 * needs to be done for buildForm should be done here
295 * This is a virtual function and should be redefined if needed
301 public function preProcess() {}
304 * This function is called after the form is validated. Any
305 * processing of form state etc should be done in this function.
306 * Typically all processing associated with a form should be done
307 * here and relevant state should be stored in the session
309 * This is a virtual function and should be redefined if needed
315 public function postProcess() {}
318 * This function is just a wrapper, so that we can call all the hook functions
319 * @param bool $allowAjax - FIXME: This feels kind of hackish, ideally we would take the json-related code from this function
320 * and bury it deeper down in the controller
322 public function mainProcess($allowAjax = TRUE) {
323 $this->postProcess();
324 $this->postProcessHook();
326 // Respond with JSON if in AJAX context (also support legacy value '6')
327 if ($allowAjax && !empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
328 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
329 $this->ajaxResponse
['action'] = $this->_action
;
330 if (isset($this->_id
) ||
isset($this->id
)) {
331 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
333 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
338 * The postProcess hook is typically called by the framework
339 * However in a few cases, the form exits or redirects early in which
340 * case it needs to call this function so other modules can do the needful
341 * Calling this function directly should be avoided if possible. In general a
342 * better way is to do setUserContext so the framework does the redirect
345 public function postProcessHook() {
346 CRM_Utils_Hook
::postProcess(get_class($this), $this);
350 * This virtual function is used to build the form. It replaces the
351 * buildForm associated with QuickForm_Page. This allows us to put
352 * preProcess in front of the actual form building routine
358 public function buildQuickForm() {}
361 * This virtual function is used to set the default values of
362 * various form elements
366 * @return array reference to the array of default values
369 public function setDefaultValues() {}
372 * This is a virtual function that adds group and global rules to
373 * the form. Keeping it distinct from the form to keep code small
374 * and localized in the form building code
380 public function addRules() {}
383 * Performs the server side validation
385 * @return boolean true if no error found
386 * @throws HTML_QuickForm_Error
388 public function validate() {
389 $error = parent
::validate();
391 $this->validateChainSelectFields();
393 $hookErrors = CRM_Utils_Hook
::validate(
395 $this->_submitValues
,
400 if (!is_array($hookErrors)) {
401 $hookErrors = array();
404 CRM_Utils_Hook
::validateForm(
406 $this->_submitValues
,
412 if (!empty($hookErrors)) {
413 $this->_errors +
= $hookErrors;
416 return (0 == count($this->_errors
));
420 * Core function that builds the form. We redefine this function
421 * here and expect all CRM forms to build their form in the function
425 public function buildForm() {
426 $this->_formBuilt
= TRUE;
430 CRM_Utils_Hook
::preProcess(get_class($this), $this);
432 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
435 $this->controller
->_key
&&
436 $this->controller
->_generateQFKey
438 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
439 $this->assign('qfKey', $this->controller
->_key
);
443 // _generateQFKey suppresses the qfKey generation on form snippets that
444 // are part of other forms, hence we use that to avoid adding entryURL
445 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
446 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
449 $this->buildQuickForm();
451 $defaults = $this->setDefaultValues();
452 unset($defaults['qfKey']);
454 if (!empty($defaults)) {
455 $this->setDefaults($defaults);
458 // call the form hook
459 // also call the hook function so any modules can set thier own custom defaults
460 // the user can do both the form and set default values with this hook
461 CRM_Utils_Hook
::buildForm(get_class($this), $this);
465 //Set html data-attribute to enable warning user of unsaved changes
466 if ($this->unsavedChangesWarn
=== true
467 ||
(!isset($this->unsavedChangesWarn
)
468 && ($this->_action
& CRM_Core_Action
::ADD ||
$this->_action
& CRM_Core_Action
::UPDATE
)
471 $this->setAttribute('data-warn-changes', 'true');
476 * Add default Next / Back buttons
478 * @param array array of associative arrays in the order in which the buttons should be
479 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
480 * The base form class will define a bunch of static arrays for commonly used
487 public function addButtons($params) {
490 foreach ($params as $button) {
491 $js = CRM_Utils_Array
::value('js', $button);
492 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
494 $attrs = array('class' => 'crm-form-submit default');
497 $attrs = array('class' => 'crm-form-submit');
501 $attrs = array_merge($js, $attrs);
504 if ($button['type'] === 'cancel') {
505 $attrs['class'] .= ' cancel';
508 if ($button['type'] === 'reset') {
509 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
512 if (!empty($button['subName'])) {
513 $buttonName = $this->getButtonName($button['type'], $button['subName']);
516 $buttonName = $this->getButtonName($button['type']);
519 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
520 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
522 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
524 if (!empty($button['isDefault'])) {
525 $this->setDefaultAction($button['type']);
528 // if button type is upload, set the enctype
529 if ($button['type'] == 'upload') {
530 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
531 $this->setMaxFileSize();
534 // hack - addGroup uses an array to express variable spacing, read from the last element
535 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
537 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
541 * Getter function for Name
545 public function getName() {
550 * Getter function for State
554 public function &getState() {
555 return $this->_state
;
559 * Getter function for StateType
563 public function getStateType() {
564 return $this->_state
->getType();
568 * Getter function for title. Should be over-ridden by derived class
572 public function getTitle() {
573 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
577 * Setter function for title.
579 * @param string $title the title of the form
583 public function setTitle($title) {
584 $this->_title
= $title;
588 * Setter function for options
594 public function setOptions($options) {
595 $this->_options
= $options;
599 * Getter function for link.
603 public function getLink() {
604 $config = CRM_Core_Config
::singleton();
605 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
606 '_qf_' . $this->_name
. '_display=true'
611 * Boolean function to determine if this is a one form page
615 public function isSimpleForm() {
616 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
620 * Getter function for Form Action
624 public function getFormAction() {
625 return $this->_attributes
['action'];
629 * Setter function for Form Action
635 public function setFormAction($action) {
636 $this->_attributes
['action'] = $action;
640 * Render form and return contents
644 public function toSmarty() {
645 $this->preProcessChainSelectFields();
646 $renderer = $this->getRenderer();
647 $this->accept($renderer);
648 $content = $renderer->toArray();
649 $content['formName'] = $this->getName();
651 $content['formClass'] = CRM_Utils_System
::getClassName($this);
656 * Getter function for renderer. If renderer is not set
657 * create one and initialize it
661 public function &getRenderer() {
662 if (!isset($this->_renderer
)) {
663 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
665 return $this->_renderer
;
669 * Use the form name to create the tpl file name
673 public function getTemplateFileName() {
674 $ext = CRM_Extension_System
::singleton()->getMapper();
675 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
676 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
677 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
680 $tplname = str_replace('_',
682 CRM_Utils_System
::getClassName($this)
689 * A wrapper for getTemplateFileName that includes calling the hook to
690 * prevent us from having to copy & paste the logic of calling the hook
692 public function getHookedTemplateFileName() {
693 $pageTemplateFile = $this->getTemplateFileName();
694 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
695 return $pageTemplateFile;
699 * Default extra tpl file basically just replaces .tpl with .extra.tpl
700 * i.e. we dont override
704 public function overrideExtraTemplateFileName() {
709 * Error reporting mechanism
711 * @param string $message Error Message
712 * @param int $code Error Code
713 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
717 public function error($message, $code = NULL, $dao = NULL) {
719 $dao->query('ROLLBACK');
722 $error = CRM_Core_Error
::singleton();
724 $error->push($code, $message);
728 * Store the variable with the value in the form scope
730 * @param string name : name of the variable
731 * @param mixed value : value of the variable
737 public function set($name, $value) {
738 $this->controller
->set($name, $value);
742 * Get the variable from the form scope
744 * @param string name : name of the variable
750 public function get($name) {
751 return $this->controller
->get($name);
759 public function getAction() {
760 return $this->_action
;
766 * @param int $action the mode we want to set the form
770 public function setAction($action) {
771 $this->_action
= $action;
775 * Assign value to name in template
777 * @param string $var name of variable
778 * @param mixed $value value of variable
782 public function assign($var, $value = NULL) {
783 self
::$_template->assign($var, $value);
787 * Assign value to name in template by reference
789 * @param string $var name of variable
790 * @param mixed $value value of varaible
794 public function assign_by_ref($var, &$value) {
795 self
::$_template->assign_by_ref($var, $value);
799 * Appends values to template variables
801 * @param array|string $tpl_var the template variable name(s)
802 * @param mixed $value the value to append
805 public function append($tpl_var, $value=NULL, $merge=FALSE) {
806 self
::$_template->append($tpl_var, $value, $merge);
810 * Returns an array containing template variables
812 * @param string $name
816 public function get_template_vars($name=null) {
817 return self
::$_template->get_template_vars($name);
821 * @param string $name
824 * @param array $attributes
825 * @param null $separator
826 * @param bool $required
828 * @return HTML_QuickForm_group
830 public function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
832 $attributes = $attributes ?
$attributes : array();
833 $allowClear = !empty($attributes['allowClear']);
834 unset($attributes['allowClear']);
835 $attributes +
= array('id_suffix' => $name);
836 foreach ($values as $key => $var) {
837 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
839 $group = $this->addGroup($options, $name, $title, $separator);
841 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
844 $group->setAttribute('allowClear', TRUE);
852 * @param bool $allowClear
853 * @param null $required
854 * @param array $attributes
856 public function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
857 $attributes +
= array('id_suffix' => $id);
859 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
860 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
862 $group = $this->addGroup($choice, $id, $title);
864 $group->setAttribute('allowClear', TRUE);
867 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
876 * @param null $attributes
877 * @param null $required
878 * @param null $javascriptMethod
879 * @param string $separator
880 * @param bool $flipValues
882 function addCheckBox($id, $title, $values, $other = NULL,
883 $attributes = NULL, $required = NULL,
884 $javascriptMethod = NULL,
885 $separator = '<br />', $flipValues = FALSE
889 if ($javascriptMethod) {
890 foreach ($values as $key => $var) {
892 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
895 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
900 foreach ($values as $key => $var) {
902 $options[] = $this->createElement('checkbox', $var, NULL, $key);
905 $options[] = $this->createElement('checkbox', $key, NULL, $var);
910 $this->addGroup($options, $id, $title, $separator);
913 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
918 ts('%1 is a required field.', array(1 => $title)),
924 public function resetValues() {
925 $data = $this->controller
->container();
926 $data['values'][$this->_name
] = array();
930 * Simple shell that derived classes can call to add buttons to
931 * the form with a customized title for the main Submit
933 * @param string $title title of the main button
934 * @param string $nextType button type for the form after processing
935 * @param string $backType
936 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
940 public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
942 if ($backType != NULL) {
945 'name' => ts('Previous'),
948 if ($nextType != NULL) {
955 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
957 $buttons[] = $nextButton;
959 $this->addButtons($buttons);
963 * @param string $name
964 * @param string $from
966 * @param string $label
967 * @param string $dateFormat
968 * @param bool $required
969 * @param bool $displayTime
971 public function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
973 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
974 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
976 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
977 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
982 * Adds a select based on field metadata
983 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
984 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
985 * @param $name - field name to go on the form
986 * @param array $props - mix of html attributes and special properties, namely
987 * - entity (api entity name, can usually be inferred automatically from the form class)
988 * - field (field name - only needed if different from name used on the form)
989 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
990 * - placeholder - set to NULL to disable
992 * - context - @see CRM_Core_DAO::buildOptionsContext
993 * @param bool $required
994 * @throws CRM_Core_Exception
995 * @return HTML_QuickForm_Element
997 public function addSelect($name, $props = array(), $required = FALSE) {
998 if (!isset($props['entity'])) {
999 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
1001 if (!isset($props['field'])) {
1002 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
1004 // Fetch options from the api unless passed explicitly
1005 if (isset($props['options'])) {
1006 $options = $props['options'];
1009 $info = civicrm_api3($props['entity'], 'getoptions', $props);
1010 $options = $info['values'];
1012 if (!array_key_exists('placeholder', $props)) {
1013 $props['placeholder'] = $required ?
ts('- select -') : CRM_Utils_Array
::value('context', $props) == 'search' ?
ts('- any -') : ts('- none -');
1015 // Handle custom field
1016 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1017 list(, $id) = explode('_', $name);
1018 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
1019 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
1020 if (CRM_Utils_Array
::value('context', $props) != 'search') {
1021 $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);
1026 $info = civicrm_api3($props['entity'], 'getfields');
1027 foreach($info['values'] as $uniqueName => $fieldSpec) {
1029 $uniqueName === $props['field'] ||
1030 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
1031 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
1036 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
1037 if (CRM_Utils_Array
::value('context', $props) != 'search') {
1038 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
1041 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
1042 $props['data-api-entity'] = $props['entity'];
1043 $props['data-api-field'] = $props['field'];
1044 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url', 'options', 'context');
1045 return $this->add('select', $name, $label, $options, $required, $props);
1049 * Add a widget for selecting/editing/creating/copying a profile form
1051 * @param string $name HTML form-element name
1052 * @param string $label Printable label
1053 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
1054 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
1055 * @param array $entities
1056 * @param bool $default //CRM-15427
1058 public function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities, $default = FALSE) {
1060 // FIXME: Instead of adhoc serialization, use a single json_encode()
1061 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
1062 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1063 $this->add('text', $name, $label, array(
1064 'class' => 'crm-profile-selector',
1065 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1066 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1067 'data-entities' => json_encode($entities),
1069 'data-default' => $default,
1074 * @param string $name
1076 * @param $attributes
1077 * @param bool $forceTextarea
1079 public function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1080 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1081 // 2. Based on the option, initialise proper editor
1082 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1085 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1086 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1088 if (!$editor ||
$forceTextarea) {
1089 $editor = 'textarea';
1091 if ($editor == 'joomla default editor') {
1092 $editor = 'joomlaeditor';
1095 if ($editor == 'drupal default editor') {
1096 $editor = 'drupalwysiwyg';
1099 //lets add the editor as a attribute
1100 $attributes['editor'] = $editor;
1102 $this->addElement($editor, $name, $label, $attributes);
1103 $this->assign('editor', $editor);
1105 // include wysiwyg editor js files
1106 // FIXME: This code does not make any sense
1107 $includeWysiwygEditor = FALSE;
1108 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1109 if (!$includeWysiwygEditor) {
1110 $includeWysiwygEditor = TRUE;
1111 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1114 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1120 * @param null $required
1121 * @param null $extra
1123 public function addCountry($id, $title, $required = NULL, $extra = NULL) {
1124 $this->addElement('select', $id, $title,
1126 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1129 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1134 * @param string $name
1137 * @param $attributes
1138 * @param null $required
1139 * @param null $javascriptMethod
1141 public function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1143 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1146 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1153 public function getRootTitle() {
1160 public function getCompleteTitle() {
1161 return $this->getRootTitle() . $this->getTitle();
1165 * @return CRM_Core_Smarty
1167 public static function &getTemplate() {
1168 return self
::$_template;
1172 * @param $elementName
1174 public function addUploadElement($elementName) {
1175 $uploadNames = $this->get('uploadNames');
1176 if (!$uploadNames) {
1177 $uploadNames = array();
1179 if (is_array($elementName)) {
1180 foreach ($elementName as $name) {
1181 if (!in_array($name, $uploadNames)) {
1182 $uploadNames[] = $name;
1187 if (!in_array($elementName, $uploadNames)) {
1188 $uploadNames[] = $elementName;
1191 $this->set('uploadNames', $uploadNames);
1193 $config = CRM_Core_Config
::singleton();
1194 if (!empty($uploadNames)) {
1195 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1202 public function buttonType() {
1203 $uploadNames = $this->get('uploadNames');
1204 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1205 $this->assign('buttonType', $buttonType);
1214 public function getVar($name) {
1215 return isset($this->$name) ?
$this->$name : NULL;
1222 public function setVar($name, $value) {
1223 $this->$name = $value;
1228 * @param string $name name of the element
1229 * @param string $label label of the element
1230 * @param array $attributes key / value pair
1232 * // if you need time
1233 * $attributes = array(
1234 * 'addTime' => true,
1235 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1237 * @param boolean $required true if required
1240 public function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1241 if (!empty($attributes['formatType'])) {
1242 // get actual format
1243 $params = array('name' => $attributes['formatType']);
1246 // cache date information
1248 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1249 if (empty($dateFormat[$key])) {
1250 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1251 $dateFormat[$key] = $values;
1254 $values = $dateFormat[$key];
1257 if ($values['date_format']) {
1258 $attributes['format'] = $values['date_format'];
1261 if (!empty($values['time_format'])) {
1262 $attributes['timeFormat'] = $values['time_format'];
1264 $attributes['startOffset'] = $values['start'];
1265 $attributes['endOffset'] = $values['end'];
1268 $config = CRM_Core_Config
::singleton();
1269 if (empty($attributes['format'])) {
1270 $attributes['format'] = $config->dateInputFormat
;
1273 if (!isset($attributes['startOffset'])) {
1274 $attributes['startOffset'] = 10;
1277 if (!isset($attributes['endOffset'])) {
1278 $attributes['endOffset'] = 10;
1281 $this->add('text', $name, $label, $attributes);
1283 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1285 if (!isset($attributes['timeFormat'])) {
1286 $timeFormat = $config->timeInputFormat
;
1289 $timeFormat = $attributes['timeFormat'];
1292 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1294 $show24Hours = TRUE;
1295 if ($timeFormat == 1) {
1296 $show24Hours = FALSE;
1299 //CRM-6664 -we are having time element name
1300 //in either flat string or an array format.
1301 $elementName = $name . '_time';
1302 if (substr($name, -1) == ']') {
1303 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1306 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1311 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1312 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1313 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1319 * Function that will add date and time
1321 public function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1322 $addTime = array('addTime' => TRUE);
1323 if (is_array($attributes)) {
1324 $attributes = array_merge($attributes, $addTime);
1327 $attributes = $addTime;
1330 $this->addDate($name, $label, $required, $attributes);
1334 * Add a currency and money element to the form
1336 function addMoney($name,
1340 $addCurrency = TRUE,
1341 $currencyName = 'currency',
1342 $defaultCurrency = NULL,
1343 $freezeCurrency = FALSE
1345 $element = $this->add('text', $name, $label, $attributes, $required);
1346 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1349 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1356 * Add currency element to the form
1358 function addCurrency($name = 'currency',
1361 $defaultCurrency = NULL,
1362 $freezeCurrency = FALSE
1364 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1365 $options = array('class' => 'crm-select2 eight');
1367 $currencies = array('' => '') +
$currencies;
1368 $options['placeholder'] = ts('- none -');
1370 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1371 if ($freezeCurrency) {
1374 if (!$defaultCurrency) {
1375 $config = CRM_Core_Config
::singleton();
1376 $defaultCurrency = $config->defaultCurrency
;
1378 $this->setDefaults(array($name => $defaultCurrency));
1382 * Create a single or multiple entity ref field
1383 * @param string $name
1384 * @param string $label
1385 * @param array $props mix of html and widget properties, including:
1386 * - select - params to give to select2 widget
1387 * - entity - defaults to contact
1388 * - create - can the user create a new entity on-the-fly?
1389 * Set to TRUE if entity is contact and you want the default profiles,
1390 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1391 * note that permissions are checked automatically
1392 * - api - array of settings for the getlist api wrapper
1393 * note that it accepts a 'params' setting which will be passed to the underlying api
1394 * - placeholder - string
1396 * - class, etc. - other html properties
1397 * @param bool $required
1399 * @return HTML_QuickForm_Element
1401 public function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1402 require_once "api/api.php";
1403 $config = CRM_Core_Config
::singleton();
1404 // Default properties
1405 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1406 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1407 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1409 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1410 unset($props['create']);
1413 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1415 $defaults = array();
1416 if (!empty($props['multiple'])) {
1417 $defaults['multiple'] = TRUE;
1419 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1421 $this->formatReferenceFieldAttributes($props);
1422 return $this->add('text', $name, $label, $props, $required);
1428 private function formatReferenceFieldAttributes(&$props) {
1429 $props['data-select-params'] = json_encode($props['select']);
1430 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1431 $props['data-api-entity'] = $props['entity'];
1432 if (!empty($props['create'])) {
1433 $props['data-create-links'] = json_encode($props['create']);
1435 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1439 * Convert all date fields within the params to mysql date ready for the
1440 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1441 * and if time is defined it is incorporated
1443 * @param array $params input params from the form
1445 * @todo it would probably be better to work on $this->_params than a passed array
1446 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1449 public function convertDateFieldsToMySQL(&$params){
1450 foreach ($this->_dateFields
as $fieldName => $specs){
1451 if(!empty($params[$fieldName])){
1452 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1453 CRM_Utils_Date
::processDate(
1454 $params[$fieldName],
1455 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1459 if(isset($specs['default'])){
1460 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1467 * @param $elementName
1469 public function removeFileRequiredRules($elementName) {
1470 $this->_required
= array_diff($this->_required
, array($elementName));
1471 if (isset($this->_rules
[$elementName])) {
1472 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1473 if ($ruleInfo['type'] == 'uploadedfile') {
1474 unset($this->_rules
[$elementName][$index]);
1477 if (empty($this->_rules
[$elementName])) {
1478 unset($this->_rules
[$elementName]);
1484 * Function that can be defined in Form to override or
1485 * perform specific action on cancel action
1488 public function cancelAction() {}
1491 * Helper function to verify that required fields have been filled
1492 * Typically called within the scope of a FormRule function
1494 public static function validateMandatoryFields($fields, $values, &$errors) {
1495 foreach ($fields as $name => $fld) {
1496 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1497 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1503 * Get contact if for a form object. Prioritise
1504 * - cid in URL if 0 (on behalf on someoneelse)
1505 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1506 * - logged in user id if it matches the one in the cid in the URL
1507 * - contact id validated from a checksum from a checksum
1508 * - cid from the url if the caller has ACL permission to view
1509 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1511 * @return mixed NULL|integer
1513 public function getContactID() {
1514 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1515 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1516 $tempID = $this->_params
['select_contact_id'];
1518 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1519 // event form stores as an indexed array, contribution form not so much...
1520 $tempID = $this->_params
[0]['select_contact_id'];
1523 // force to ignore the authenticated user
1524 if ($tempID === '0' ||
$tempID === 0) {
1525 // we set the cid on the form so that this will be retained for the Confirm page
1526 // in the multi-page form & prevent us returning the $userID when this is called
1528 // we don't really need to set it when $tempID is set because the params have that stored
1529 $this->set('cid', 0);
1533 $userID = $this->getLoggedInUserContactID();
1535 if ($tempID == $userID) {
1539 //check if this is a checksum authentication
1540 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1541 if ($userChecksum) {
1542 //check for anonymous user.
1543 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1548 // check if user has permission, CRM-12062
1549 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1557 * Get the contact id of the logged in user
1559 public function getLoggedInUserContactID() {
1560 // check if the user is logged in and has a contact ID
1561 $session = CRM_Core_Session
::singleton();
1562 return $session->get('userID');
1566 * Add autoselector field -if user has permission to view contacts
1567 * If adding this to a form you also need to add to the tpl e.g
1569 * {if !empty($selectable)}
1570 * <div class="crm-summary-row">
1571 * <div class="crm-label">{$form.select_contact.label}</div>
1572 * <div class="crm-content">
1573 * {$form.select_contact.html}
1578 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1579 * @param array $autoCompleteField
1583 * - url (for ajax lookup)
1585 * @todo add data attributes so we can deal with multiple instances on a form
1587 public function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1588 $autoCompleteField = array_merge(array(
1589 'id_field' => 'select_contact_id',
1590 'placeholder' => ts('Select someone else ...'),
1591 'show_hide' => TRUE,
1592 'api' => array('params' => array('contact_type' => 'Individual'))
1593 ), $autoCompleteField);
1595 if($this->canUseAjaxContactLookups()) {
1596 $this->assign('selectable', $autoCompleteField['id_field']);
1597 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1599 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js', 1, 'html-header')
1601 'form' => array('autocompletes' => $autoCompleteField),
1602 'ids' => array('profile' => $profiles),
1610 public function canUseAjaxContactLookups() {
1611 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1612 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1618 * Add the options appropriate to cid = zero - ie. autocomplete
1620 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1621 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1622 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1623 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1625 public function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1626 $this->assign('nocid', TRUE);
1627 $profiles = array();
1628 if($this->_values
['custom_pre_id']) {
1629 $profiles[] = $this->_values
['custom_pre_id'];
1631 if($this->_values
['custom_post_id']) {
1632 $profiles = array_merge($profiles, (array) $this->_values
['custom_post_id']);
1634 if($onlinePaymentProcessorEnabled) {
1635 $profiles[] = 'billing';
1637 if(!empty($this->_values
)) {
1638 $this->addAutoSelector($profiles);
1643 * Set default values on form for given contact (or no contact defaults)
1645 * @param mixed $profile_id (can be id, or profile name)
1646 * @param integer $contactID
1650 public function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1652 $defaults = civicrm_api3('profile', 'getsingle', array(
1653 'profile_id' => (array) $profile_id,
1654 'contact_id' => $contactID,
1658 catch (Exception
$e) {
1659 // the try catch block gives us silent failure -not 100% sure this is a good idea
1660 // as silent failures are often worse than noisy ones
1666 * Sets form attribute
1669 public function preventAjaxSubmit() {
1670 $this->setAttribute('data-no-ajax-submit', 'true');
1674 * Sets form attribute
1677 public function allowAjaxSubmit() {
1678 $this->removeAttribute('data-no-ajax-submit');
1682 * Sets page title based on entity and action
1683 * @param string $entityLabel
1685 public function setPageTitle($entityLabel) {
1686 switch ($this->_action
) {
1687 case CRM_Core_Action
::ADD
:
1688 CRM_Utils_System
::setTitle(ts('New %1', array(1 => $entityLabel)));
1690 case CRM_Core_Action
::UPDATE
:
1691 CRM_Utils_System
::setTitle(ts('Edit %1', array(1 => $entityLabel)));
1693 case CRM_Core_Action
::VIEW
:
1694 case CRM_Core_Action
::PREVIEW
:
1695 CRM_Utils_System
::setTitle(ts('View %1', array(1 => $entityLabel)));
1697 case CRM_Core_Action
::DELETE
:
1698 CRM_Utils_System
::setTitle(ts('Delete %1', array(1 => $entityLabel)));
1704 * Create a chain-select target field. All settings are optional; the defaults usually work.
1706 * @param string $elementName
1707 * @param array $settings
1709 * @return HTML_QuickForm_Element
1711 public function addChainSelect($elementName, $settings = array()) {
1712 $props = $settings +
= array(
1713 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array('country', 'Country', 'state_province', 'StateProvince'), $elementName),
1714 'data-callback' => strpos($elementName, 'rovince') ?
'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
1715 'label' => strpos($elementName, 'rovince') ?
ts('State/Province') : ts('County'),
1716 'data-empty-prompt' => strpos($elementName, 'rovince') ?
ts('Choose country first') : ts('Choose state first'),
1717 'data-none-prompt' => ts('- N/A -'),
1718 'multiple' => FALSE,
1719 'required' => FALSE,
1720 'placeholder' => empty($settings['required']) ?
ts('- none -') : ts('- select -'),
1722 CRM_Utils_Array
::remove($props, 'label', 'required', 'control_field');
1723 $props['class'] = (empty($props['class']) ?
'' : "{$props['class']} ") . 'crm-select2';
1724 $props['data-select-prompt'] = $props['placeholder'];
1725 $props['data-name'] = $elementName;
1727 $this->_chainSelectFields
[$settings['control_field']] = $elementName;
1729 // Passing NULL instead of an array of options
1730 // CRM-15225 - normally QF will reject any selected values that are not part of the field's options, but due to a
1731 // quirk in our patched version of HTML_QuickForm_select, this doesn't happen if the options are NULL
1732 // which seems a bit dirty but it allows our dynamically-popuplated select element to function as expected.
1733 return $this->add('select', $elementName, $settings['label'], NULL, $settings['required'], $props);
1737 * Set options and attributes for chain select fields based on the controlling field's value
1739 private function preProcessChainSelectFields() {
1740 foreach ($this->_chainSelectFields
as $control => $target) {
1741 $targetField = $this->getElement($target);
1742 $targetType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ?
'county' : 'stateProvince';
1744 // If the control field is on the form, setup chain-select and dynamically populate options
1745 if ($this->elementExists($control)) {
1746 $controlField = $this->getElement($control);
1747 $controlType = $targetType == 'county' ?
'stateProvince' : 'country';
1749 $targetField->setAttribute('class', $targetField->getAttribute('class') . ' crm-chain-select-target');
1751 $css = (string) $controlField->getAttribute('class');
1752 $controlField->updateAttributes(array(
1753 'class' => ($css ?
"$css " : 'crm-select2 ') . 'crm-chain-select-control',
1754 'data-target' => $target,
1756 $controlValue = $controlField->getValue();
1757 if ($controlValue) {
1758 $options = CRM_Core_BAO_Location
::getChainSelectValues($controlValue, $controlType, TRUE);
1760 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
1763 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
1764 $targetField->setAttribute('disabled', 'disabled');
1767 // Control field not present - fall back to loading default options
1769 $options = CRM_Core_PseudoConstant
::$targetType();
1771 if (!$targetField->getAttribute('multiple')) {
1772 $options = array('' => $targetField->getAttribute('placeholder')) +
$options;
1773 $targetField->removeAttribute('placeholder');
1775 $targetField->_options
= array();
1776 $targetField->loadArray($options);
1781 * Validate country / state / county match and suppress unwanted "required" errors
1783 private function validateChainSelectFields() {
1784 foreach ($this->_chainSelectFields
as $control => $target) {
1785 if ($this->elementExists($control)) {
1786 $controlValue = (array)$this->getElementValue($control);
1787 $targetField = $this->getElement($target);
1788 $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ?
'stateProvince' : 'country';
1789 $targetValue = array_filter((array)$targetField->getValue());
1790 if ($targetValue ||
$this->getElementError($target)) {
1791 $options = CRM_Core_BAO_Location
::getChainSelectValues($controlValue, $controlType, TRUE);
1793 if (!array_intersect($targetValue, array_keys($options))) {
1794 $this->setElementError($target, $controlType == 'country' ?
ts('State/Province does not match the selected Country') : ts('County does not match the selected State/Province'));
1796 } // Suppress "required" error for field if it has no options
1797 elseif (!$options) {
1798 $this->setElementError($target, NULL);