3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.4 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
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-2013
39 require_once 'HTML/QuickForm/Page.php';
40 class CRM_Core_Form
extends HTML_QuickForm_Page
{
43 * The state object that this form belongs to
49 * The name of this form
55 * The title of this form
58 protected $_title = NULL;
61 * The options passed into this form
64 protected $_options = NULL;
67 * The mode of operation for this form
73 * the renderer used for this form
80 * An array to hold a list of datefields on the form
81 * so that they can be converted to ISO in a consistent manner
85 * e.g on a form declare $_dateFields = array(
86 * 'receive_date' => array('default' => 'now'),
88 * then in postProcess call $this->convertDateFieldsToMySQL($formValues)
89 * to have the time field re-incorporated into the field & 'now' set if
90 * no value has been passed in
92 protected $_dateFields = array();
95 * cache the smarty template for efficiency reasons
97 * @var CRM_Core_Smarty
99 static protected $_template;
102 * What to return to the client if in ajax mode (snippet=json)
106 public $ajaxResponse = array();
109 * Stores info about reference fields for preprocessing
110 * Public so that hooks can access it
114 public $entityReferenceFields = array();
117 * constants for attributes for various form elements
118 * attempt to standardize on the number of variations that we
119 * use of the below form elements
123 CONST ATTR_SPACING
= ' ';
126 * All checkboxes are defined with a common prefix. This allows us to
127 * have the same javascript to check / clear all the checkboxes etc
128 * If u have multiple groups of checkboxes, you will need to give them different
129 * ids to avoid potential name collision
131 * @var const string / int
133 CONST CB_PREFIX
= 'mark_x_', CB_PREFIY
= 'mark_y_', CB_PREFIZ
= 'mark_z_', CB_PREFIX_LEN
= 7;
136 * Constructor for the basic form page
138 * We should not use QuickForm directly. This class provides a lot
139 * of default convenient functions, rules and buttons
141 * @param object $state State associated with this form
142 * @param enum $action The mode the form is operating in (None/Create/View/Update/Delete)
143 * @param string $method The type of http method used (GET/POST)
144 * @param string $name The name of the form if different from class name
149 function __construct(
151 $action = CRM_Core_Action
::NONE
,
157 $this->_name
= $name;
160 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
163 $this->HTML_QuickForm_Page($this->_name
, $method);
165 $this->_state
=& $state;
167 $this->_state
->setName($this->_name
);
169 $this->_action
= (int) $action;
171 $this->registerRules();
173 // let the constructor initialize this, should happen only once
174 if (!isset(self
::$_template)) {
175 self
::$_template = CRM_Core_Smarty
::singleton();
178 $this->assign('snippet', (int) CRM_Utils_Array
::value('snippet', $_REQUEST));
181 static function generateID() {
185 * register all the standard rules that most forms potentially use
191 function registerRules() {
192 static $rules = array(
193 'title', 'longTitle', 'variable', 'qfVariable',
194 'phone', 'integer', 'query',
196 'domain', 'numberOfDigit',
197 'date', 'currentDate',
198 'asciiFile', 'htmlFile', 'utf8File',
199 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
200 'xssString', 'fileExists', 'autocomplete', 'validContact',
203 foreach ($rules as $rule) {
204 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
209 * Simple easy to use wrapper around addElement. Deal with
210 * simple validation rules
212 * @param string type of html element to be added
213 * @param string name of the html element
214 * @param string display label for the html element
215 * @param string attributes used for this element.
216 * These are not default values
217 * @param bool is this a required field
219 * @return HTML_QuickForm_Element could be an error object
223 function &add($type, $name, $label = '',
224 $attributes = '', $required = FALSE, $javascript = NULL
226 $element = $this->addElement($type, $name, $label, $attributes, $javascript);
227 if (HTML_QuickForm
::isError($element)) {
228 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
232 if ($type == 'file') {
233 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
236 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
238 if (HTML_QuickForm
::isError($error)) {
239 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
247 * This function is called before buildForm. Any pre-processing that
248 * needs to be done for buildForm should be done here
250 * This is a virtual function and should be redefined if needed
257 function preProcess() {}
260 * This function is called after the form is validated. Any
261 * processing of form state etc should be done in this function.
262 * Typically all processing associated with a form should be done
263 * here and relevant state should be stored in the session
265 * This is a virtual function and should be redefined if needed
272 function postProcess() {}
275 * This function is just a wrapper, so that we can call all the hook functions
277 function mainProcess() {
278 $this->postProcess();
279 $this->postProcessHook();
281 // Respond with JSON if in AJAX context (also support legacy value '6')
282 if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
283 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
284 $this->ajaxResponse
['action'] = $this->_action
;
285 if (isset($this->_id
) ||
isset($this->id
)) {
286 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
288 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
293 * The postProcess hook is typically called by the framework
294 * However in a few cases, the form exits or redirects early in which
295 * case it needs to call this function so other modules can do the needful
296 * Calling this function directly should be avoided if possible. In general a
297 * better way is to do setUserContext so the framework does the redirect
300 function postProcessHook() {
301 CRM_Utils_Hook
::postProcess(get_class($this), $this);
305 * This virtual function is used to build the form. It replaces the
306 * buildForm associated with QuickForm_Page. This allows us to put
307 * preProcess in front of the actual form building routine
314 function buildQuickForm() {}
317 * This virtual function is used to set the default values of
318 * various form elements
322 * @return array reference to the array of default values
325 function setDefaultValues() {}
328 * This is a virtual function that adds group and global rules to
329 * the form. Keeping it distinct from the form to keep code small
330 * and localized in the form building code
337 function addRules() {}
339 function validate() {
340 $error = parent
::validate();
342 $hookErrors = CRM_Utils_Hook
::validate(
344 $this->_submitValues
,
349 if (!is_array($hookErrors)) {
350 $hookErrors = array();
353 CRM_Utils_Hook
::validateForm(
355 $this->_submitValues
,
361 if (!empty($hookErrors)) {
362 $this->_errors +
= $hookErrors;
365 return (0 == count($this->_errors
));
369 * Core function that builds the form. We redefine this function
370 * here and expect all CRM forms to build their form in the function
374 function buildForm() {
375 $this->_formBuilt
= TRUE;
379 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
382 $this->controller
->_key
&&
383 $this->controller
->_generateQFKey
385 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
386 $this->assign('qfKey', $this->controller
->_key
);
390 // _generateQFKey suppresses the qfKey generation on form snippets that
391 // are part of other forms, hence we use that to avoid adding entryURL
392 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
393 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
396 $this->buildQuickForm();
398 $defaults = $this->setDefaultValues();
399 unset($defaults['qfKey']);
401 if (!empty($defaults)) {
402 $this->setDefaults($defaults);
405 // call the form hook
406 // also call the hook function so any modules can set thier own custom defaults
407 // the user can do both the form and set default values with this hook
408 CRM_Utils_Hook
::buildForm(get_class($this), $this);
410 $this->preprocessReferenceFields();
416 * Add default Next / Back buttons
418 * @param array array of associative arrays in the order in which the buttons should be
419 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
420 * The base form class will define a bunch of static arrays for commonly used
428 function addButtons($params) {
431 foreach ($params as $button) {
432 $js = CRM_Utils_Array
::value('js', $button);
433 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
435 $attrs = array('class' => 'form-submit default');
438 $attrs = array('class' => 'form-submit');
442 $attrs = array_merge($js, $attrs);
445 if ($button['type'] === 'reset') {
446 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
449 if (!empty($button['subName'])) {
450 $buttonName = $this->getButtonName($button['type'], $button['subName']);
453 $buttonName = $this->getButtonName($button['type']);
456 if (in_array($button['type'], array(
457 'next', 'upload')) && $button['name'] === 'Save') {
458 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
460 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
462 if (!empty($button['isDefault'])) {
463 $this->setDefaultAction($button['type']);
466 // if button type is upload, set the enctype
467 if ($button['type'] == 'upload') {
468 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
469 $this->setMaxFileSize();
472 // hack - addGroup uses an array to express variable spacing, read from the last element
473 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
475 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
479 * getter function for Name
489 * getter function for State
494 function &getState() {
495 return $this->_state
;
499 * getter function for StateType
504 function getStateType() {
505 return $this->_state
->getType();
509 * getter function for title. Should be over-ridden by derived class
514 function getTitle() {
515 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
519 * setter function for title.
521 * @param string $title the title of the form
526 function setTitle($title) {
527 $this->_title
= $title;
531 * Setter function for options
538 function setOptions($options) {
539 $this->_options
= $options;
543 * getter function for link.
549 $config = CRM_Core_Config
::singleton();
550 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
551 '_qf_' . $this->_name
. '_display=true'
556 * boolean function to determine if this is a one form page
561 function isSimpleForm() {
562 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
566 * getter function for Form Action
571 function getFormAction() {
572 return $this->_attributes
['action'];
576 * setter function for Form Action
583 function setFormAction($action) {
584 $this->_attributes
['action'] = $action;
588 * render form and return contents
593 function toSmarty() {
594 $renderer = $this->getRenderer();
595 $this->accept($renderer);
596 $content = $renderer->toArray();
597 $content['formName'] = $this->getName();
602 * getter function for renderer. If renderer is not set
603 * create one and initialize it
608 function &getRenderer() {
609 if (!isset($this->_renderer
)) {
610 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
612 return $this->_renderer
;
616 * Use the form name to create the tpl file name
621 function getTemplateFileName() {
622 $ext = CRM_Extension_System
::singleton()->getMapper();
623 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
624 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
625 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
628 $tplname = str_replace('_',
630 CRM_Utils_System
::getClassName($this)
637 * A wrapper for getTemplateFileName that includes calling the hook to
638 * prevent us from having to copy & paste the logic of calling the hook
640 function getHookedTemplateFileName() {
641 $pageTemplateFile = $this->getTemplateFileName();
642 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
643 return $pageTemplateFile;
647 * Default extra tpl file basically just replaces .tpl with .extra.tpl
648 * i.e. we dont override
653 function overrideExtraTemplateFileName() {
658 * Error reporting mechanism
660 * @param string $message Error Message
661 * @param int $code Error Code
662 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
667 function error($message, $code = NULL, $dao = NULL) {
669 $dao->query('ROLLBACK');
672 $error = CRM_Core_Error
::singleton();
674 $error->push($code, $message);
678 * Store the variable with the value in the form scope
680 * @param string name : name of the variable
681 * @param mixed value : value of the variable
688 function set($name, $value) {
689 $this->controller
->set($name, $value);
693 * Get the variable from the form scope
695 * @param string name : name of the variable
702 function get($name) {
703 return $this->controller
->get($name);
712 function getAction() {
713 return $this->_action
;
719 * @param int $action the mode we want to set the form
724 function setAction($action) {
725 $this->_action
= $action;
729 * assign value to name in template
731 * @param array|string $name name of variable
732 * @param mixed $value value of varaible
737 function assign($var, $value = NULL) {
738 self
::$_template->assign($var, $value);
742 * assign value to name in template by reference
744 * @param array|string $name name of variable
745 * @param mixed $value value of varaible
750 function assign_by_ref($var, &$value) {
751 self
::$_template->assign_by_ref($var, $value);
755 * appends values to template variables
757 * @param array|string $tpl_var the template variable name(s)
758 * @param mixed $value the value to append
761 function append($tpl_var, $value=NULL, $merge=FALSE) {
762 self
::$_template->append($tpl_var, $value, $merge);
766 * Returns an array containing template variables
768 * @param string $name
769 * @param string $type
772 function get_template_vars($name=null) {
773 return self
::$_template->get_template_vars($name);
776 function &addRadio($name, $title, &$values, $attributes = NULL, $separator = NULL, $required = FALSE) {
778 if (empty($attributes)) {
779 $attributes = array('id_suffix' => $name);
782 $attributes = array_merge($attributes, array('id_suffix' => $name));
784 foreach ($values as $key => $var) {
785 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
787 $group = $this->addGroup($options, $name, $title, $separator);
789 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
794 function addYesNo($id, $title, $dontKnow = NULL, $required = NULL, $attribute = NULL) {
795 if (empty($attribute)) {
796 $attribute = array('id_suffix' => $id);
799 $attribute = array_merge($attribute, array('id_suffix' => $id));
802 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attribute);
803 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attribute);
805 $choice[] = $this->createElement('radio', NULL, '22', ts("Don't Know"), '2', $attribute);
807 $this->addGroup($choice, $id, $title);
810 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
814 function addCheckBox($id, $title, $values, $other = NULL,
815 $attributes = NULL, $required = NULL,
816 $javascriptMethod = NULL,
817 $separator = '<br />', $flipValues = FALSE
821 if ($javascriptMethod) {
822 foreach ($values as $key => $var) {
824 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
827 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
832 foreach ($values as $key => $var) {
834 $options[] = $this->createElement('checkbox', $var, NULL, $key);
837 $options[] = $this->createElement('checkbox', $key, NULL, $var);
842 $this->addGroup($options, $id, $title, $separator);
845 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
850 ts('%1 is a required field.', array(1 => $title)),
856 function resetValues() {
857 $data = $this->controller
->container();
858 $data['values'][$this->_name
] = array();
862 * simple shell that derived classes can call to add buttons to
863 * the form with a customized title for the main Submit
865 * @param string $title title of the main button
866 * @param string $type button type for the form after processing
867 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
872 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
874 if ($backType != NULL) {
877 'name' => ts('Previous'),
880 if ($nextType != NULL) {
887 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
889 $buttons[] = $nextButton;
891 $this->addButtons($buttons);
894 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
896 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
897 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
899 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
900 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
904 function addSelect($name, $label, $prefix = NULL, $required = NULL, $extra = NULL, $select = '- select -') {
906 $this->addElement('select', $name . '_id' . $prefix, $label,
908 '' => $select) + CRM_Core_OptionGroup
::values($name), $extra
911 $this->addRule($name . '_id' . $prefix, ts('Please select %1', array(1 => $label)), 'required');
915 $this->addElement('select', $name . '_id', $label,
917 '' => $select) + CRM_Core_OptionGroup
::values($name), $extra
920 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
926 * Add a widget for selecting/editing/creating/copying a profile form
928 * @param string $name HTML form-element name
929 * @param string $label Printable label
930 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
931 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
932 * @param array $entities
934 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
936 // FIXME: Instead of adhoc serialization, use a single json_encode()
937 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
938 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
939 $this->add('text', $name, $label, array(
940 'class' => 'crm-profile-selector',
941 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
942 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
943 'data-entities' => json_encode($entities),
947 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
948 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
949 // 2. Based on the option, initialise proper editor
950 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
953 $editor = strtolower(CRM_Utils_Array
::value($editorID,
954 CRM_Core_OptionGroup
::values('wysiwyg_editor')
956 if (!$editor ||
$forceTextarea) {
957 $editor = 'textarea';
959 if ($editor == 'joomla default editor') {
960 $editor = 'joomlaeditor';
963 if ($editor == 'drupal default editor') {
964 $editor = 'drupalwysiwyg';
967 //lets add the editor as a attribute
968 $attributes['editor'] = $editor;
970 $this->addElement($editor, $name, $label, $attributes);
971 $this->assign('editor', $editor);
973 // include wysiwyg editor js files
974 // FIXME: This code does not make any sense
975 $includeWysiwygEditor = FALSE;
976 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
977 if (!$includeWysiwygEditor) {
978 $includeWysiwygEditor = TRUE;
979 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
982 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
985 function addCountry($id, $title, $required = NULL, $extra = NULL) {
986 $this->addElement('select', $id, $title,
988 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
991 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
995 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
997 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1000 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1004 public function getRootTitle() {
1008 public function getCompleteTitle() {
1009 return $this->getRootTitle() . $this->getTitle();
1012 static function &getTemplate() {
1013 return self
::$_template;
1016 function addUploadElement($elementName) {
1017 $uploadNames = $this->get('uploadNames');
1018 if (!$uploadNames) {
1019 $uploadNames = array();
1021 if (is_array($elementName)) {
1022 foreach ($elementName as $name) {
1023 if (!in_array($name, $uploadNames)) {
1024 $uploadNames[] = $name;
1029 if (!in_array($elementName, $uploadNames)) {
1030 $uploadNames[] = $elementName;
1033 $this->set('uploadNames', $uploadNames);
1035 $config = CRM_Core_Config
::singleton();
1036 if (!empty($uploadNames)) {
1037 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1041 function buttonType() {
1042 $uploadNames = $this->get('uploadNames');
1043 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1044 $this->assign('buttonType', $buttonType);
1048 function getVar($name) {
1049 return isset($this->$name) ?
$this->$name : NULL;
1052 function setVar($name, $value) {
1053 $this->$name = $value;
1057 * Function to add date
1058 * @param string $name name of the element
1059 * @param string $label label of the element
1060 * @param array $attributes key / value pair
1063 * $attributes = array ( 'addTime' => true,
1064 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1066 * @param boolean $required true if required
1069 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1070 if (!empty($attributes['formatType'])) {
1071 // get actual format
1072 $params = array('name' => $attributes['formatType']);
1075 // cache date information
1077 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1078 if (empty($dateFormat[$key])) {
1079 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1080 $dateFormat[$key] = $values;
1083 $values = $dateFormat[$key];
1086 if ($values['date_format']) {
1087 $attributes['format'] = $values['date_format'];
1090 if (!empty($values['time_format'])) {
1091 $attributes['timeFormat'] = $values['time_format'];
1093 $attributes['startOffset'] = $values['start'];
1094 $attributes['endOffset'] = $values['end'];
1097 $config = CRM_Core_Config
::singleton();
1098 if (empty($attributes['format'])) {
1099 $attributes['format'] = $config->dateInputFormat
;
1102 if (!isset($attributes['startOffset'])) {
1103 $attributes['startOffset'] = 10;
1106 if (!isset($attributes['endOffset'])) {
1107 $attributes['endOffset'] = 10;
1110 $this->add('text', $name, $label, $attributes);
1112 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1114 if (!isset($attributes['timeFormat'])) {
1115 $timeFormat = $config->timeInputFormat
;
1118 $timeFormat = $attributes['timeFormat'];
1121 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1123 $show24Hours = TRUE;
1124 if ($timeFormat == 1) {
1125 $show24Hours = FALSE;
1128 //CRM-6664 -we are having time element name
1129 //in either flat string or an array format.
1130 $elementName = $name . '_time';
1131 if (substr($name, -1) == ']') {
1132 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1135 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1140 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1141 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1142 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1148 * Function that will add date and time
1150 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1151 $addTime = array('addTime' => TRUE);
1152 if (is_array($attributes)) {
1153 $attributes = array_merge($attributes, $addTime);
1156 $attributes = $addTime;
1159 $this->addDate($name, $label, $required, $attributes);
1163 * add a currency and money element to the form
1165 function addMoney($name,
1169 $addCurrency = TRUE,
1170 $currencyName = 'currency',
1171 $defaultCurrency = NULL,
1172 $freezeCurrency = FALSE
1174 $element = $this->add('text', $name, $label, $attributes, $required);
1175 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1178 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1185 * add currency element to the form
1187 function addCurrency($name = 'currency',
1190 $defaultCurrency = NULL,
1191 $freezeCurrency = FALSE
1193 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1195 $currencies = array(
1196 '' => ts('- select -')) +
$currencies;
1198 $ele = $this->add('select', $name, $label, $currencies, $required);
1199 if ($freezeCurrency) {
1202 if (!$defaultCurrency) {
1203 $config = CRM_Core_Config
::singleton();
1204 $defaultCurrency = $config->defaultCurrency
;
1206 $this->setDefaults(array($name => $defaultCurrency));
1210 * Create a single or multiple contact ref field
1211 * @param string $name
1212 * @param string $label
1213 * @param array $props mix of html and widget properties, including:
1214 * - select - params to give to select2 widget
1215 * - api - array of settings for the api, keys include "entity", "action", "params", "search", "key", "label"
1216 * - placeholder - string
1218 * - class, etc. - other html properties
1219 * @param bool $required
1220 * @return HTML_QuickForm_Element
1222 function addEntityRef($name, $label, $props = array(), $required = FALSE) {
1223 $props['class'] = isset($props['class']) ?
$props['class'] . ' crm-select2' : 'crm-select2';
1225 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
array(
1226 'minimumInputLength' => 1,
1227 'multiple' => !empty($props['multiple']),
1228 'placeholder' => CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select -') : ts('- none -')),
1229 'allowClear' => !$required,
1230 // Disabled pending https://github.com/ivaynberg/select2/pull/2092
1231 //'formatInputTooShort' => ts('Start typing a name or email address...'),
1232 //'formatNoMatches' => ts('No contacts found.'),
1235 $props['api'] = CRM_Utils_Array
::value('api', $props, array()) +
array(
1236 'entity' => 'contact',
1237 'action' => 'getquick',
1243 $this->entityReferenceFields
[] = $name;
1244 $this->formatReferenceFieldAttributes($props);
1245 return $this->add('text', $name, $label, $props, $required);
1251 private function formatReferenceFieldAttributes(&$props) {
1252 $props['data-select-params'] = json_encode($props['select']);
1253 $props['data-api-params'] = json_encode($props['api']);
1254 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'placeholder');
1258 * Convert IDs to values and format for display
1260 private function preprocessReferenceFields() {
1261 foreach ($this->entityReferenceFields
as $name) {
1262 $field = $this->getElement($name);
1263 $val = $field->getValue();
1264 // Support array values
1265 if (is_array($val)) {
1266 $val = implode(',', $val);
1267 $field->setValue($val);
1270 $data = $labels = array();
1271 $api = json_decode($field->getAttribute('data-api-params'), TRUE);
1272 $select = json_decode($field->getAttribute('data-select-params'), TRUE);
1273 // Support serialized values
1274 if (strpos($val, CRM_Core_DAO
::VALUE_SEPARATOR
) !== FALSE) {
1275 $val = str_replace(CRM_Core_DAO
::VALUE_SEPARATOR
, ',', trim($val, CRM_Core_DAO
::VALUE_SEPARATOR
));
1276 $field->setValue($val);
1278 foreach (explode(',', $val) as $v) {
1279 $result = civicrm_api3($api['entity'], $api['action'], array('sequential' => 1, $api['key'] => $v));
1280 if (!empty($result['values'])) {
1281 $data[] = array('id' => $v, 'text' => $result['values'][0][$api['label']]);
1282 $labels[] = $result['values'][0][$api['label']];
1285 if ($field->isFrozen()) {
1286 $field->removeAttribute('class');
1287 $field->setValue(implode(', ', $labels));
1290 if (empty($select['multiple'])) {
1293 $field->setAttribute('data-entity-value', json_encode($data));
1300 * Convert all date fields within the params to mysql date ready for the
1301 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1302 * and if time is defined it is incorporated
1304 * @param array $params input params from the form
1306 * @todo it would probably be better to work on $this->_params than a passed array
1307 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1310 function convertDateFieldsToMySQL(&$params){
1311 foreach ($this->_dateFields
as $fieldName => $specs){
1312 if(!empty($params[$fieldName])){
1313 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1314 CRM_Utils_Date
::processDate(
1315 $params[$fieldName],
1316 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1320 if(isset($specs['default'])){
1321 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1327 function removeFileRequiredRules($elementName) {
1328 $this->_required
= array_diff($this->_required
, array($elementName));
1329 if (isset($this->_rules
[$elementName])) {
1330 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1331 if ($ruleInfo['type'] == 'uploadedfile') {
1332 unset($this->_rules
[$elementName][$index]);
1335 if (empty($this->_rules
[$elementName])) {
1336 unset($this->_rules
[$elementName]);
1342 * Function that can be defined in Form to override or
1343 * perform specific action on cancel action
1347 function cancelAction() {}
1350 * Helper function to verify that required fields have been filled
1351 * Typically called within the scope of a FormRule function
1353 static function validateMandatoryFields($fields, $values, &$errors) {
1354 foreach ($fields as $name => $fld) {
1355 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1356 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1362 * Get contact if for a form object. Prioritise
1363 * - cid in URL if 0 (on behalf on someoneelse)
1364 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1365 * - logged in user id if it matches the one in the cid in the URL
1366 * - contact id validated from a checksum from a checksum
1367 * - cid from the url if the caller has ACL permission to view
1368 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1370 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1372 function getContactID() {
1373 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1374 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1375 $tempID = $this->_params
['select_contact_id'];
1377 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1378 // event form stores as an indexed array, contribution form not so much...
1379 $tempID = $this->_params
[0]['select_contact_id'];
1382 // force to ignore the authenticated user
1383 if ($tempID === '0' ||
$tempID === 0) {
1384 // we set the cid on the form so that this will be retained for the Confirm page
1385 // in the multi-page form & prevent us returning the $userID when this is called
1387 // we don't really need to set it when $tempID is set because the params have that stored
1388 $this->set('cid', 0);
1392 $userID = $this->getLoggedInUserContactID();
1394 if ($tempID == $userID) {
1398 //check if this is a checksum authentication
1399 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1400 if ($userChecksum) {
1401 //check for anonymous user.
1402 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1407 // check if user has permission, CRM-12062
1408 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1416 * Get the contact id of the logged in user
1418 function getLoggedInUserContactID() {
1419 // check if the user is logged in and has a contact ID
1420 $session = CRM_Core_Session
::singleton();
1421 return $session->get('userID');
1425 * add autoselector field -if user has permission to view contacts
1426 * If adding this to a form you also need to add to the tpl e.g
1428 * {if !empty($selectable)}
1429 * <div class="crm-summary-row">
1430 * <div class="crm-label">{$form.select_contact.label}</div>
1431 * <div class="crm-content">
1432 * {$form.select_contact.html}
1436 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1437 * @param array $field metadata of field to use as selector including
1440 * - url (for ajax lookup)
1442 * @todo add data attributes so we can deal with multiple instances on a form
1444 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1445 $autoCompleteField = array_merge(array(
1446 'name_field' => 'select_contact',
1447 'id_field' => 'select_contact_id',
1448 'field_text' => ts('Select Contact'),
1449 'show_hide' => TRUE,
1450 'show_text' => ts('to select someone already in our database.'),
1451 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
1452 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
1453 'max' => civicrm_api3('setting', 'getvalue', array(
1454 'name' => 'search_autocomplete_count',
1455 'group' => 'Search Preferences',
1457 ), $autoCompleteField);
1459 if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
1460 $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
1461 $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
1462 $this->assign('selectable', $autoCompleteField['id_field']);
1464 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
1466 'form' => array('autocompletes' => $autoCompleteField),
1467 'ids' => array('profile' => $profiles),
1473 * Add the options appropriate to cid = zero - ie. autocomplete
1475 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1476 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1477 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1478 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1480 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1481 $this->assign('nocid', TRUE);
1482 $profiles = array();
1483 if($this->_values
['custom_pre_id']) {
1484 $profiles[] = $this->_values
['custom_pre_id'];
1486 if($this->_values
['custom_post_id']) {
1487 $profiles[] = $this->_values
['custom_post_id'];
1489 if($onlinePaymentProcessorEnabled) {
1490 $profiles[] = 'billing';
1492 if(!empty($this->_values
)) {
1493 $this->addAutoSelector($profiles);
1498 * Set default values on form for given contact (or no contact defaults)
1499 * @param mixed $profile_id (can be id, or profile name)
1500 * @param integer $contactID
1502 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1504 $defaults = civicrm_api3('profile', 'getsingle', array(
1505 'profile_id' => (array) $profile_id,
1506 'contact_id' => $contactID,
1510 catch (Exception
$e) {
1511 // the try catch block gives us silent failure -not 100% sure this is a good idea
1512 // as silent failures are often worse than noisy ones