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) {
499 $prevnext = $spacing = array();
500 foreach ($params as $button) {
501 $attrs = array('class' => 'crm-form-submit') +
(array) CRM_Utils_Array
::value('js', $button);
503 if (!empty($button['isDefault'])) {
504 $attrs['class'] .= ' default';
507 if (in_array($button['type'], array('upload', 'next', 'submit', 'done', 'process', 'refresh'))) {
508 $attrs['class'] .= ' validate';
509 $defaultIcon = 'check';
512 $attrs['class'] .= ' cancel';
513 $defaultIcon = $button['type'] == 'back' ?
'triangle-1-w' : 'close';
516 if ($button['type'] === 'reset') {
517 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
520 if (!empty($button['subName'])) {
521 if ($button['subName'] == 'new') {
522 $defaultIcon = 'plus';
524 if ($button['subName'] == 'done') {
525 $defaultIcon = 'circle-check';
527 if ($button['subName'] == 'next') {
528 $defaultIcon = 'circle-triangle-e';
532 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
533 $attrs['accesskey'] = 'S';
535 $icon = CRM_Utils_Array
::value('icon', $button, $defaultIcon);
537 $attrs['crm-icon'] = $icon;
539 $buttonName = $this->getButtonName($button['type'], CRM_Utils_Array
::value('subName', $button));
540 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
542 if (!empty($button['isDefault'])) {
543 $this->setDefaultAction($button['type']);
546 // if button type is upload, set the enctype
547 if ($button['type'] == 'upload') {
548 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
549 $this->setMaxFileSize();
552 // hack - addGroup uses an array to express variable spacing, read from the last element
553 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
555 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
559 * Getter function for Name
563 public function getName() {
568 * Getter function for State
572 public function &getState() {
573 return $this->_state
;
577 * Getter function for StateType
581 public function getStateType() {
582 return $this->_state
->getType();
586 * Getter function for title. Should be over-ridden by derived class
590 public function getTitle() {
591 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
595 * Setter function for title.
597 * @param string $title
598 * The title of the form.
602 public function setTitle($title) {
603 $this->_title
= $title;
607 * Setter function for options
613 public function setOptions($options) {
614 $this->_options
= $options;
618 * Getter function for link.
622 public function getLink() {
623 $config = CRM_Core_Config
::singleton();
624 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
625 '_qf_' . $this->_name
. '_display=true'
630 * Boolean function to determine if this is a one form page
634 public function isSimpleForm() {
635 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
639 * Getter function for Form Action
643 public function getFormAction() {
644 return $this->_attributes
['action'];
648 * Setter function for Form Action
654 public function setFormAction($action) {
655 $this->_attributes
['action'] = $action;
659 * Render form and return contents
663 public function toSmarty() {
664 $this->preProcessChainSelectFields();
665 $renderer = $this->getRenderer();
666 $this->accept($renderer);
667 $content = $renderer->toArray();
668 $content['formName'] = $this->getName();
670 $content['formClass'] = CRM_Utils_System
::getClassName($this);
675 * Getter function for renderer. If renderer is not set
676 * create one and initialize it
680 public function &getRenderer() {
681 if (!isset($this->_renderer
)) {
682 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
684 return $this->_renderer
;
688 * Use the form name to create the tpl file name
692 public function getTemplateFileName() {
693 $ext = CRM_Extension_System
::singleton()->getMapper();
694 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
695 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
696 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
699 $tplname = str_replace('_',
701 CRM_Utils_System
::getClassName($this)
708 * A wrapper for getTemplateFileName that includes calling the hook to
709 * prevent us from having to copy & paste the logic of calling the hook
711 public function getHookedTemplateFileName() {
712 $pageTemplateFile = $this->getTemplateFileName();
713 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
714 return $pageTemplateFile;
718 * Default extra tpl file basically just replaces .tpl with .extra.tpl
719 * i.e. we dont override
723 public function overrideExtraTemplateFileName() {
728 * Error reporting mechanism
730 * @param string $message
734 * @param CRM_Core_DAO $dao
735 * A data access object on which we perform a rollback if non - empty.
739 public function error($message, $code = NULL, $dao = NULL) {
741 $dao->query('ROLLBACK');
744 $error = CRM_Core_Error
::singleton();
746 $error->push($code, $message);
750 * Store the variable with the value in the form scope
752 * @param string name : name of the variable
753 * @param mixed value : value of the variable
759 public function set($name, $value) {
760 $this->controller
->set($name, $value);
764 * Get the variable from the form scope
766 * @param string name : name of the variable
772 public function get($name) {
773 return $this->controller
->get($name);
781 public function getAction() {
782 return $this->_action
;
789 * The mode we want to set the form.
793 public function setAction($action) {
794 $this->_action
= $action;
798 * Assign value to name in template
802 * @param mixed $value
807 public function assign($var, $value = NULL) {
808 self
::$_template->assign($var, $value);
812 * Assign value to name in template by reference
816 * @param mixed $value
821 public function assign_by_ref($var, &$value) {
822 self
::$_template->assign_by_ref($var, $value);
826 * Appends values to template variables
828 * @param array|string $tpl_var the template variable name(s)
829 * @param mixed $value
830 * The value to append.
833 public function append($tpl_var, $value = NULL, $merge = FALSE) {
834 self
::$_template->append($tpl_var, $value, $merge);
838 * Returns an array containing template variables
840 * @param string $name
844 public function get_template_vars($name = NULL) {
845 return self
::$_template->get_template_vars($name);
849 * @param string $name
852 * @param array $attributes
853 * @param null $separator
854 * @param bool $required
856 * @return HTML_QuickForm_group
858 public function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
860 $attributes = $attributes ?
$attributes : array();
861 $allowClear = !empty($attributes['allowClear']);
862 unset($attributes['allowClear']);
863 $attributes +
= array('id_suffix' => $name);
864 foreach ($values as $key => $var) {
865 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
867 $group = $this->addGroup($options, $name, $title, $separator);
869 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
872 $group->setAttribute('allowClear', TRUE);
880 * @param bool $allowClear
881 * @param null $required
882 * @param array $attributes
884 public function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
885 $attributes +
= array('id_suffix' => $id);
887 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
888 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
890 $group = $this->addGroup($choice, $id, $title);
892 $group->setAttribute('allowClear', TRUE);
895 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
904 * @param null $attributes
905 * @param null $required
906 * @param null $javascriptMethod
907 * @param string $separator
908 * @param bool $flipValues
910 function addCheckBox(
911 $id, $title, $values, $other = NULL,
912 $attributes = NULL, $required = NULL,
913 $javascriptMethod = NULL,
914 $separator = '<br />', $flipValues = FALSE
918 if ($javascriptMethod) {
919 foreach ($values as $key => $var) {
921 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
924 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
929 foreach ($values as $key => $var) {
931 $options[] = $this->createElement('checkbox', $var, NULL, $key);
934 $options[] = $this->createElement('checkbox', $key, NULL, $var);
939 $this->addGroup($options, $id, $title, $separator);
942 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
947 ts('%1 is a required field.', array(1 => $title)),
953 public function resetValues() {
954 $data = $this->controller
->container();
955 $data['values'][$this->_name
] = array();
959 * Simple shell that derived classes can call to add buttons to
960 * the form with a customized title for the main Submit
962 * @param string $title
963 * Title of the main button.
964 * @param string $nextType
965 * Button type for the form after processing.
966 * @param string $backType
967 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
971 public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
973 if ($backType != NULL) {
976 'name' => ts('Previous'),
979 if ($nextType != NULL) {
986 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
988 $buttons[] = $nextButton;
990 $this->addButtons($buttons);
994 * @param string $name
995 * @param string $from
997 * @param string $label
998 * @param string $dateFormat
999 * @param bool $required
1000 * @param bool $displayTime
1002 public function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
1004 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
1005 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1008 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
1009 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1014 * Adds a select based on field metadata
1015 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
1016 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
1018 * Field name to go on the form.
1019 * @param array $props
1020 * Mix of html attributes and special properties, namely.
1021 * - entity (api entity name, can usually be inferred automatically from the form class)
1022 * - field (field name - only needed if different from name used on the form)
1023 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
1024 * - placeholder - set to NULL to disable
1026 * - context - @see CRM_Core_DAO::buildOptionsContext
1027 * @param bool $required
1028 * @throws CRM_Core_Exception
1029 * @return HTML_QuickForm_Element
1031 public function addSelect($name, $props = array(), $required = FALSE) {
1032 if (!isset($props['entity'])) {
1033 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
1035 if (!isset($props['field'])) {
1036 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
1038 // Fetch options from the api unless passed explicitly
1039 if (isset($props['options'])) {
1040 $options = $props['options'];
1043 $info = civicrm_api3($props['entity'], 'getoptions', $props);
1044 $options = $info['values'];
1046 if (!array_key_exists('placeholder', $props)) {
1047 $props['placeholder'] = $required ?
ts('- select -') : CRM_Utils_Array
::value('context', $props) == 'search' ?
ts('- any -') : ts('- none -');
1049 // Handle custom field
1050 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1051 list(, $id) = explode('_', $name);
1052 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
1053 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
1054 if (CRM_Utils_Array
::value('context', $props) != 'search') {
1055 $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);
1060 $info = civicrm_api3($props['entity'], 'getfields');
1061 foreach ($info['values'] as $uniqueName => $fieldSpec) {
1063 $uniqueName === $props['field'] ||
1064 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
1065 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
1070 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
1071 if (CRM_Utils_Array
::value('context', $props) != 'search') {
1072 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
1075 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
1076 $props['data-api-entity'] = $props['entity'];
1077 $props['data-api-field'] = $props['field'];
1078 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url', 'options', 'context');
1079 return $this->add('select', $name, $label, $options, $required, $props);
1083 * Add a widget for selecting/editing/creating/copying a profile form
1085 * @param string $name
1086 * HTML form-element name.
1087 * @param string $label
1089 * @param string $allowCoreTypes
1090 * Only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'.
1091 * @param string $allowSubTypes
1092 * Only present a UFGroup if its group_type is compatible with $allowSubypes.
1093 * @param array $entities
1094 * @param bool $default
1097 public function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities, $default = FALSE) {
1099 // FIXME: Instead of adhoc serialization, use a single json_encode()
1100 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
1101 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1102 $this->add('text', $name, $label, array(
1103 'class' => 'crm-profile-selector',
1104 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1105 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1106 'data-entities' => json_encode($entities),
1108 'data-default' => $default,
1113 * @param string $name
1115 * @param $attributes
1116 * @param bool $forceTextarea
1118 public function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1119 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1120 // 2. Based on the option, initialise proper editor
1121 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1124 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1125 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1127 if (!$editor ||
$forceTextarea) {
1128 $editor = 'textarea';
1130 if ($editor == 'joomla default editor') {
1131 $editor = 'joomlaeditor';
1134 if ($editor == 'drupal default editor') {
1135 $editor = 'drupalwysiwyg';
1138 //lets add the editor as a attribute
1139 $attributes['editor'] = $editor;
1141 $this->addElement($editor, $name, $label, $attributes);
1142 $this->assign('editor', $editor);
1144 // include wysiwyg editor js files
1145 // FIXME: This code does not make any sense
1146 $includeWysiwygEditor = FALSE;
1147 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1148 if (!$includeWysiwygEditor) {
1149 $includeWysiwygEditor = TRUE;
1150 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1153 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1159 * @param null $required
1160 * @param null $extra
1162 public function addCountry($id, $title, $required = NULL, $extra = NULL) {
1163 $this->addElement('select', $id, $title,
1165 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1168 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1173 * @param string $name
1176 * @param $attributes
1177 * @param null $required
1178 * @param null $javascriptMethod
1180 public function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1182 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1185 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1192 public function getRootTitle() {
1199 public function getCompleteTitle() {
1200 return $this->getRootTitle() . $this->getTitle();
1204 * @return CRM_Core_Smarty
1206 public static function &getTemplate() {
1207 return self
::$_template;
1211 * @param $elementName
1213 public function addUploadElement($elementName) {
1214 $uploadNames = $this->get('uploadNames');
1215 if (!$uploadNames) {
1216 $uploadNames = array();
1218 if (is_array($elementName)) {
1219 foreach ($elementName as $name) {
1220 if (!in_array($name, $uploadNames)) {
1221 $uploadNames[] = $name;
1226 if (!in_array($elementName, $uploadNames)) {
1227 $uploadNames[] = $elementName;
1230 $this->set('uploadNames', $uploadNames);
1232 $config = CRM_Core_Config
::singleton();
1233 if (!empty($uploadNames)) {
1234 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1241 public function buttonType() {
1242 $uploadNames = $this->get('uploadNames');
1243 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1244 $this->assign('buttonType', $buttonType);
1253 public function getVar($name) {
1254 return isset($this->$name) ?
$this->$name : NULL;
1261 public function setVar($name, $value) {
1262 $this->$name = $value;
1267 * @param string $name
1268 * Name of the element.
1269 * @param string $label
1270 * Label of the element.
1271 * @param array $attributes
1274 * // if you need time
1275 * $attributes = array(
1276 * 'addTime' => true,
1277 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1279 * @param bool $required
1283 public function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1284 if (!empty($attributes['formatType'])) {
1285 // get actual format
1286 $params = array('name' => $attributes['formatType']);
1289 // cache date information
1291 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1292 if (empty($dateFormat[$key])) {
1293 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1294 $dateFormat[$key] = $values;
1297 $values = $dateFormat[$key];
1300 if ($values['date_format']) {
1301 $attributes['format'] = $values['date_format'];
1304 if (!empty($values['time_format'])) {
1305 $attributes['timeFormat'] = $values['time_format'];
1307 $attributes['startOffset'] = $values['start'];
1308 $attributes['endOffset'] = $values['end'];
1311 $config = CRM_Core_Config
::singleton();
1312 if (empty($attributes['format'])) {
1313 $attributes['format'] = $config->dateInputFormat
;
1316 if (!isset($attributes['startOffset'])) {
1317 $attributes['startOffset'] = 10;
1320 if (!isset($attributes['endOffset'])) {
1321 $attributes['endOffset'] = 10;
1324 $this->add('text', $name, $label, $attributes);
1326 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1328 if (!isset($attributes['timeFormat'])) {
1329 $timeFormat = $config->timeInputFormat
;
1332 $timeFormat = $attributes['timeFormat'];
1335 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1337 $show24Hours = TRUE;
1338 if ($timeFormat == 1) {
1339 $show24Hours = FALSE;
1342 //CRM-6664 -we are having time element name
1343 //in either flat string or an array format.
1344 $elementName = $name . '_time';
1345 if (substr($name, -1) == ']') {
1346 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1349 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1354 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1355 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1356 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1362 * Function that will add date and time
1364 public function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1365 $addTime = array('addTime' => TRUE);
1366 if (is_array($attributes)) {
1367 $attributes = array_merge($attributes, $addTime);
1370 $attributes = $addTime;
1373 $this->addDate($name, $label, $required, $attributes);
1377 * Add a currency and money element to the form
1384 $addCurrency = TRUE,
1385 $currencyName = 'currency',
1386 $defaultCurrency = NULL,
1387 $freezeCurrency = FALSE
1389 $element = $this->add('text', $name, $label, $attributes, $required);
1390 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1393 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1400 * Add currency element to the form
1402 function addCurrency(
1406 $defaultCurrency = NULL,
1407 $freezeCurrency = FALSE
1409 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1410 $options = array('class' => 'crm-select2 eight');
1412 $currencies = array('' => '') +
$currencies;
1413 $options['placeholder'] = ts('- none -');
1415 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1416 if ($freezeCurrency) {
1419 if (!$defaultCurrency) {
1420 $config = CRM_Core_Config
::singleton();
1421 $defaultCurrency = $config->defaultCurrency
;
1423 $this->setDefaults(array($name => $defaultCurrency));
1427 * Create a single or multiple entity ref field
1428 * @param string $name
1429 * @param string $label
1430 * @param array $props
1431 * Mix of html and widget properties, including:.
1432 * - select - params to give to select2 widget
1433 * - entity - defaults to contact
1434 * - create - can the user create a new entity on-the-fly?
1435 * Set to TRUE if entity is contact and you want the default profiles,
1436 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1437 * note that permissions are checked automatically
1438 * - api - array of settings for the getlist api wrapper
1439 * note that it accepts a 'params' setting which will be passed to the underlying api
1440 * - placeholder - string
1442 * - class, etc. - other html properties
1443 * @param bool $required
1445 * @return HTML_QuickForm_Element
1447 public function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1448 require_once "api/api.php";
1449 $config = CRM_Core_Config
::singleton();
1450 // Default properties
1451 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1452 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1453 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1455 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1456 unset($props['create']);
1459 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1461 $defaults = array();
1462 if (!empty($props['multiple'])) {
1463 $defaults['multiple'] = TRUE;
1465 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1467 $this->formatReferenceFieldAttributes($props);
1468 return $this->add('text', $name, $label, $props, $required);
1474 private function formatReferenceFieldAttributes(&$props) {
1475 $props['data-select-params'] = json_encode($props['select']);
1476 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1477 $props['data-api-entity'] = $props['entity'];
1478 if (!empty($props['create'])) {
1479 $props['data-create-links'] = json_encode($props['create']);
1481 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1485 * Convert all date fields within the params to mysql date ready for the
1486 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1487 * and if time is defined it is incorporated
1489 * @param array $params
1490 * Input params from the form.
1492 * @todo it would probably be better to work on $this->_params than a passed array
1493 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1496 public function convertDateFieldsToMySQL(&$params) {
1497 foreach ($this->_dateFields
as $fieldName => $specs) {
1498 if (!empty($params[$fieldName])) {
1499 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1500 CRM_Utils_Date
::processDate(
1501 $params[$fieldName],
1502 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1506 if (isset($specs['default'])) {
1507 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1514 * @param $elementName
1516 public function removeFileRequiredRules($elementName) {
1517 $this->_required
= array_diff($this->_required
, array($elementName));
1518 if (isset($this->_rules
[$elementName])) {
1519 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1520 if ($ruleInfo['type'] == 'uploadedfile') {
1521 unset($this->_rules
[$elementName][$index]);
1524 if (empty($this->_rules
[$elementName])) {
1525 unset($this->_rules
[$elementName]);
1531 * Function that can be defined in Form to override or
1532 * perform specific action on cancel action
1535 public function cancelAction() {
1539 * Helper function to verify that required fields have been filled
1540 * Typically called within the scope of a FormRule function
1542 public static function validateMandatoryFields($fields, $values, &$errors) {
1543 foreach ($fields as $name => $fld) {
1544 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1545 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1551 * Get contact if for a form object. Prioritise
1552 * - cid in URL if 0 (on behalf on someoneelse)
1553 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1554 * - logged in user id if it matches the one in the cid in the URL
1555 * - contact id validated from a checksum from a checksum
1556 * - cid from the url if the caller has ACL permission to view
1557 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1559 * @return mixed NULL|integer
1561 public function getContactID() {
1562 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1563 if (isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1564 $tempID = $this->_params
['select_contact_id'];
1566 if (isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1567 // event form stores as an indexed array, contribution form not so much...
1568 $tempID = $this->_params
[0]['select_contact_id'];
1571 // force to ignore the authenticated user
1572 if ($tempID === '0' ||
$tempID === 0) {
1573 // we set the cid on the form so that this will be retained for the Confirm page
1574 // in the multi-page form & prevent us returning the $userID when this is called
1576 // we don't really need to set it when $tempID is set because the params have that stored
1577 $this->set('cid', 0);
1581 $userID = $this->getLoggedInUserContactID();
1583 if ($tempID == $userID) {
1587 //check if this is a checksum authentication
1588 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1589 if ($userChecksum) {
1590 //check for anonymous user.
1591 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1596 // check if user has permission, CRM-12062
1597 elseif ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1605 * Get the contact id of the logged in user
1607 public function getLoggedInUserContactID() {
1608 // check if the user is logged in and has a contact ID
1609 $session = CRM_Core_Session
::singleton();
1610 return $session->get('userID');
1614 * Add autoselector field -if user has permission to view contacts
1615 * If adding this to a form you also need to add to the tpl e.g
1617 * {if !empty($selectable)}
1618 * <div class="crm-summary-row">
1619 * <div class="crm-label">{$form.select_contact.label}</div>
1620 * <div class="crm-content">
1621 * {$form.select_contact.html}
1626 * @param array $profiles
1627 * Ids of profiles that are on the form (to be autofilled).
1628 * @param array $autoCompleteField
1632 * - url (for ajax lookup)
1634 * @todo add data attributes so we can deal with multiple instances on a form
1636 public function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1637 $autoCompleteField = array_merge(array(
1638 'id_field' => 'select_contact_id',
1639 'placeholder' => ts('Select someone else ...'),
1640 'show_hide' => TRUE,
1641 'api' => array('params' => array('contact_type' => 'Individual')),
1642 ), $autoCompleteField);
1644 if ($this->canUseAjaxContactLookups()) {
1645 $this->assign('selectable', $autoCompleteField['id_field']);
1646 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1648 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js', 1, 'html-header')
1650 'form' => array('autocompletes' => $autoCompleteField),
1651 'ids' => array('profile' => $profiles),
1659 public function canUseAjaxContactLookups() {
1660 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1661 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1667 * Add the options appropriate to cid = zero - ie. autocomplete
1669 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1670 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1671 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1672 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1674 public function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1675 $this->assign('nocid', TRUE);
1676 $profiles = array();
1677 if ($this->_values
['custom_pre_id']) {
1678 $profiles[] = $this->_values
['custom_pre_id'];
1680 if ($this->_values
['custom_post_id']) {
1681 $profiles = array_merge($profiles, (array) $this->_values
['custom_post_id']);
1683 if ($onlinePaymentProcessorEnabled) {
1684 $profiles[] = 'billing';
1686 if (!empty($this->_values
)) {
1687 $this->addAutoSelector($profiles);
1692 * Set default values on form for given contact (or no contact defaults)
1694 * @param mixed $profile_id
1695 * (can be id, or profile name).
1696 * @param int $contactID
1700 public function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1702 $defaults = civicrm_api3('profile', 'getsingle', array(
1703 'profile_id' => (array) $profile_id,
1704 'contact_id' => $contactID,
1708 catch (Exception
$e) {
1709 // the try catch block gives us silent failure -not 100% sure this is a good idea
1710 // as silent failures are often worse than noisy ones
1716 * Sets form attribute
1719 public function preventAjaxSubmit() {
1720 $this->setAttribute('data-no-ajax-submit', 'true');
1724 * Sets form attribute
1727 public function allowAjaxSubmit() {
1728 $this->removeAttribute('data-no-ajax-submit');
1732 * Sets page title based on entity and action
1733 * @param string $entityLabel
1735 public function setPageTitle($entityLabel) {
1736 switch ($this->_action
) {
1737 case CRM_Core_Action
::ADD
:
1738 CRM_Utils_System
::setTitle(ts('New %1', array(1 => $entityLabel)));
1741 case CRM_Core_Action
::UPDATE
:
1742 CRM_Utils_System
::setTitle(ts('Edit %1', array(1 => $entityLabel)));
1745 case CRM_Core_Action
::VIEW
:
1746 case CRM_Core_Action
::PREVIEW
:
1747 CRM_Utils_System
::setTitle(ts('View %1', array(1 => $entityLabel)));
1750 case CRM_Core_Action
::DELETE
:
1751 CRM_Utils_System
::setTitle(ts('Delete %1', array(1 => $entityLabel)));
1757 * Create a chain-select target field. All settings are optional; the defaults usually work.
1759 * @param string $elementName
1760 * @param array $settings
1762 * @return HTML_QuickForm_Element
1764 public function addChainSelect($elementName, $settings = array()) {
1765 $props = $settings +
= array(
1766 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array('country', 'Country', 'state_province', 'StateProvince'), $elementName),
1767 'data-callback' => strpos($elementName, 'rovince') ?
'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
1768 'label' => strpos($elementName, 'rovince') ?
ts('State/Province') : ts('County'),
1769 'data-empty-prompt' => strpos($elementName, 'rovince') ?
ts('Choose country first') : ts('Choose state first'),
1770 'data-none-prompt' => ts('- N/A -'),
1771 'multiple' => FALSE,
1772 'required' => FALSE,
1773 'placeholder' => empty($settings['required']) ?
ts('- none -') : ts('- select -'),
1775 CRM_Utils_Array
::remove($props, 'label', 'required', 'control_field');
1776 $props['class'] = (empty($props['class']) ?
'' : "{$props['class']} ") . 'crm-select2';
1777 $props['data-select-prompt'] = $props['placeholder'];
1778 $props['data-name'] = $elementName;
1780 $this->_chainSelectFields
[$settings['control_field']] = $elementName;
1782 // Passing NULL instead of an array of options
1783 // CRM-15225 - normally QF will reject any selected values that are not part of the field's options, but due to a
1784 // quirk in our patched version of HTML_QuickForm_select, this doesn't happen if the options are NULL
1785 // which seems a bit dirty but it allows our dynamically-popuplated select element to function as expected.
1786 return $this->add('select', $elementName, $settings['label'], NULL, $settings['required'], $props);
1790 * Set options and attributes for chain select fields based on the controlling field's value
1792 private function preProcessChainSelectFields() {
1793 foreach ($this->_chainSelectFields
as $control => $target) {
1794 $targetField = $this->getElement($target);
1795 $targetType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ?
'county' : 'stateProvince';
1797 // If the control field is on the form, setup chain-select and dynamically populate options
1798 if ($this->elementExists($control)) {
1799 $controlField = $this->getElement($control);
1800 $controlType = $targetType == 'county' ?
'stateProvince' : 'country';
1802 $targetField->setAttribute('class', $targetField->getAttribute('class') . ' crm-chain-select-target');
1804 $css = (string) $controlField->getAttribute('class');
1805 $controlField->updateAttributes(array(
1806 'class' => ($css ?
"$css " : 'crm-select2 ') . 'crm-chain-select-control',
1807 'data-target' => $target,
1809 $controlValue = $controlField->getValue();
1810 if ($controlValue) {
1811 $options = CRM_Core_BAO_Location
::getChainSelectValues($controlValue, $controlType, TRUE);
1813 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
1817 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
1818 $targetField->setAttribute('disabled', 'disabled');
1821 // Control field not present - fall back to loading default options
1823 $options = CRM_Core_PseudoConstant
::$targetType();
1825 if (!$targetField->getAttribute('multiple')) {
1826 $options = array('' => $targetField->getAttribute('placeholder')) +
$options;
1827 $targetField->removeAttribute('placeholder');
1829 $targetField->_options
= array();
1830 $targetField->loadArray($options);
1835 * Validate country / state / county match and suppress unwanted "required" errors
1837 private function validateChainSelectFields() {
1838 foreach ($this->_chainSelectFields
as $control => $target) {
1839 if ($this->elementExists($control)) {
1840 $controlValue = (array) $this->getElementValue($control);
1841 $targetField = $this->getElement($target);
1842 $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ?
'stateProvince' : 'country';
1843 $targetValue = array_filter((array) $targetField->getValue());
1844 if ($targetValue ||
$this->getElementError($target)) {
1845 $options = CRM_Core_BAO_Location
::getChainSelectValues($controlValue, $controlType, TRUE);
1847 if (!array_intersect($targetValue, array_keys($options))) {
1848 $this->setElementError($target, $controlType == 'country' ?
ts('State/Province does not match the selected Country') : ts('County does not match the selected State/Province'));
1850 } // Suppress "required" error for field if it has no options
1851 elseif (!$options) {
1852 $this->setElementError($target, NULL);