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));
905 * Adds a select based on field metadata
906 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
907 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
908 * @param CRM_Core_DAO $baoName - string representing bao object
910 * @param array $props
911 * @param bool $required
912 * @throws CRM_Core_Exception
913 * @return HTML_QuickForm_Element
915 function addSelect($baoName, $name, $props = array(), $required = FALSE) {
916 $options = $baoName::buildOptions($name, 'create');
917 if ($options === FALSE) {
918 throw new CRM_Core_Exception('No option list for field ' . $name);
920 if (!array_key_exists('placeholder', $props)) {
921 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
923 if (!empty($props['placeholder']) && empty($props['multiple'])) {
924 $options = array('' => '') +
$options;
927 // Handle custom field
928 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
929 list(, $id) = explode('_', $name);
930 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
931 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
932 $props['data-option-group-url'] = 'civicrm/admin/options/' . CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', $gid);
936 $meta = $bao->getFieldSpec($name);
937 $label = isset($props['label']) ?
$props['label'] : ts($meta['title']);
938 if (!empty($meta['pseudoconstant']['optionGroupName'])) {
939 $props['data-option-group-url'] = 'civicrm/admin/options/' . $meta['pseudoconstant']['optionGroupName'];
942 require_once 'api/api.php';
943 $props['data-api-entity'] = _civicrm_api_get_entity_name_from_dao($bao);
945 $props['class'] = isset($props['class']) ?
$props['class'] . ' ' : '';
946 $props['class'] .= "crm-select2";
947 CRM_Utils_Array
::remove($props, 'label');
948 return $this->add('select', $name, $label, $options, $required, $props);
952 * Add a widget for selecting/editing/creating/copying a profile form
954 * @param string $name HTML form-element name
955 * @param string $label Printable label
956 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
957 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
958 * @param array $entities
960 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
962 // FIXME: Instead of adhoc serialization, use a single json_encode()
963 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
964 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
965 $this->add('text', $name, $label, array(
966 'class' => 'crm-profile-selector',
967 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
968 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
969 'data-entities' => json_encode($entities),
973 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
974 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
975 // 2. Based on the option, initialise proper editor
976 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
979 $editor = strtolower(CRM_Utils_Array
::value($editorID,
980 CRM_Core_OptionGroup
::values('wysiwyg_editor')
982 if (!$editor ||
$forceTextarea) {
983 $editor = 'textarea';
985 if ($editor == 'joomla default editor') {
986 $editor = 'joomlaeditor';
989 if ($editor == 'drupal default editor') {
990 $editor = 'drupalwysiwyg';
993 //lets add the editor as a attribute
994 $attributes['editor'] = $editor;
996 $this->addElement($editor, $name, $label, $attributes);
997 $this->assign('editor', $editor);
999 // include wysiwyg editor js files
1000 // FIXME: This code does not make any sense
1001 $includeWysiwygEditor = FALSE;
1002 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1003 if (!$includeWysiwygEditor) {
1004 $includeWysiwygEditor = TRUE;
1005 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1008 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1011 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1012 $this->addElement('select', $id, $title,
1014 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1017 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1021 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1023 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1026 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1030 public function getRootTitle() {
1034 public function getCompleteTitle() {
1035 return $this->getRootTitle() . $this->getTitle();
1038 static function &getTemplate() {
1039 return self
::$_template;
1042 function addUploadElement($elementName) {
1043 $uploadNames = $this->get('uploadNames');
1044 if (!$uploadNames) {
1045 $uploadNames = array();
1047 if (is_array($elementName)) {
1048 foreach ($elementName as $name) {
1049 if (!in_array($name, $uploadNames)) {
1050 $uploadNames[] = $name;
1055 if (!in_array($elementName, $uploadNames)) {
1056 $uploadNames[] = $elementName;
1059 $this->set('uploadNames', $uploadNames);
1061 $config = CRM_Core_Config
::singleton();
1062 if (!empty($uploadNames)) {
1063 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1067 function buttonType() {
1068 $uploadNames = $this->get('uploadNames');
1069 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1070 $this->assign('buttonType', $buttonType);
1074 function getVar($name) {
1075 return isset($this->$name) ?
$this->$name : NULL;
1078 function setVar($name, $value) {
1079 $this->$name = $value;
1083 * Function to add date
1084 * @param string $name name of the element
1085 * @param string $label label of the element
1086 * @param array $attributes key / value pair
1089 * $attributes = array ( 'addTime' => true,
1090 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1092 * @param boolean $required true if required
1095 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1096 if (!empty($attributes['formatType'])) {
1097 // get actual format
1098 $params = array('name' => $attributes['formatType']);
1101 // cache date information
1103 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1104 if (empty($dateFormat[$key])) {
1105 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1106 $dateFormat[$key] = $values;
1109 $values = $dateFormat[$key];
1112 if ($values['date_format']) {
1113 $attributes['format'] = $values['date_format'];
1116 if (!empty($values['time_format'])) {
1117 $attributes['timeFormat'] = $values['time_format'];
1119 $attributes['startOffset'] = $values['start'];
1120 $attributes['endOffset'] = $values['end'];
1123 $config = CRM_Core_Config
::singleton();
1124 if (empty($attributes['format'])) {
1125 $attributes['format'] = $config->dateInputFormat
;
1128 if (!isset($attributes['startOffset'])) {
1129 $attributes['startOffset'] = 10;
1132 if (!isset($attributes['endOffset'])) {
1133 $attributes['endOffset'] = 10;
1136 $this->add('text', $name, $label, $attributes);
1138 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1140 if (!isset($attributes['timeFormat'])) {
1141 $timeFormat = $config->timeInputFormat
;
1144 $timeFormat = $attributes['timeFormat'];
1147 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1149 $show24Hours = TRUE;
1150 if ($timeFormat == 1) {
1151 $show24Hours = FALSE;
1154 //CRM-6664 -we are having time element name
1155 //in either flat string or an array format.
1156 $elementName = $name . '_time';
1157 if (substr($name, -1) == ']') {
1158 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1161 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1166 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1167 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1168 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1174 * Function that will add date and time
1176 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1177 $addTime = array('addTime' => TRUE);
1178 if (is_array($attributes)) {
1179 $attributes = array_merge($attributes, $addTime);
1182 $attributes = $addTime;
1185 $this->addDate($name, $label, $required, $attributes);
1189 * add a currency and money element to the form
1191 function addMoney($name,
1195 $addCurrency = TRUE,
1196 $currencyName = 'currency',
1197 $defaultCurrency = NULL,
1198 $freezeCurrency = FALSE
1200 $element = $this->add('text', $name, $label, $attributes, $required);
1201 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1204 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1211 * add currency element to the form
1213 function addCurrency($name = 'currency',
1216 $defaultCurrency = NULL,
1217 $freezeCurrency = FALSE
1219 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1221 $currencies = array(
1222 '' => ts('- select -')) +
$currencies;
1224 $ele = $this->add('select', $name, $label, $currencies, $required);
1225 if ($freezeCurrency) {
1228 if (!$defaultCurrency) {
1229 $config = CRM_Core_Config
::singleton();
1230 $defaultCurrency = $config->defaultCurrency
;
1232 $this->setDefaults(array($name => $defaultCurrency));
1236 * Create a single or multiple entity ref field
1237 * @param string $name
1238 * @param string $label
1239 * @param array $props mix of html and widget properties, including:
1240 * - select - params to give to select2 widget
1241 * - api - array of settings for the api:
1242 * - "entity" - defaults to contact
1243 * - "action" - defaults to get (getquick when entity is contact)
1244 * - "params" - additional params to pass to the api
1245 * - "key" - what to store as this field's value - defaults to "id"
1246 * - "label" - what to show to the user - defaults to "label"
1247 * - "search" - rarely used - only needed if search field is different from label field
1248 * - placeholder - string
1250 * - class, etc. - other html properties
1251 * @param bool $required
1252 * @return HTML_QuickForm_Element
1254 function addEntityRef($name, $label, $props = array(), $required = FALSE) {
1255 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1256 // Merge in defaults for api params
1257 $props['api'] +
= array(
1258 'entity' => 'contact',
1261 $props['api'] +
= array(
1262 'action' => $props['api']['entity'] == 'contact' ?
'getquick' : 'get',
1263 'label' => $props['api']['entity'] == 'contact' ?
'data' : 'label',
1265 // this can be ommitted for normal apis since search field is usually the same as label field. But getquick is bizarre.
1266 $props['api'] +
= array(
1267 'search' => $props['api']['entity'] == 'contact' ?
'name' : $props['api']['label'],
1270 $props['class'] = isset($props['class']) ?
$props['class'] . ' ' : '';
1271 $props['class'] .= "crm-select2 crm-{$props['api']['entity']}-ref-field";
1273 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
array(
1274 'minimumInputLength' => 1,
1275 'multiple' => !empty($props['multiple']),
1276 'placeholder' => CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select -') : ts('- none -')),
1277 'allowClear' => !$required,
1278 // Disabled pending https://github.com/ivaynberg/select2/pull/2092
1279 //'formatInputTooShort' => ts('Start typing a name or email address...'),
1280 //'formatNoMatches' => ts('No contacts found.'),
1283 $this->entityReferenceFields
[] = $name;
1284 $this->formatReferenceFieldAttributes($props);
1285 return $this->add('text', $name, $label, $props, $required);
1291 private function formatReferenceFieldAttributes(&$props) {
1292 $props['data-select-params'] = json_encode($props['select']);
1293 $props['data-api-params'] = json_encode($props['api']);
1294 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'placeholder');
1298 * Convert IDs to values and format for display
1300 private function preprocessReferenceFields() {
1301 foreach ($this->entityReferenceFields
as $name) {
1302 $field = $this->getElement($name);
1303 $val = $field->getValue();
1304 // Support array values
1305 if (is_array($val)) {
1306 $val = implode(',', $val);
1307 $field->setValue($val);
1311 $api = json_decode($field->getAttribute('data-api-params'), TRUE);
1312 $select = json_decode($field->getAttribute('data-select-params'), TRUE);
1313 // Support serialized values
1314 if (strpos($val, CRM_Core_DAO
::VALUE_SEPARATOR
) !== FALSE) {
1315 $val = str_replace(CRM_Core_DAO
::VALUE_SEPARATOR
, ',', trim($val, CRM_Core_DAO
::VALUE_SEPARATOR
));
1316 $field->setValue($val);
1318 // TODO: we could fetch multiple values with array(IN => $val) but not all apis support this
1319 foreach (explode(',', $val) as $v) {
1320 $result = civicrm_api3($api['entity'], $api['action'], array('sequential' => 1, $api['key'] => $v));
1321 if (!empty($result['values'])) {
1322 $data[] = array('id' => $v, 'text' => $result['values'][0][$api['label']]);
1325 if ($field->isFrozen()) {
1326 $field->removeAttribute('class');
1329 // Simplify array for single selects - makes client-side code simpler (but feels somehow wrong)
1330 if (empty($select['multiple'])) {
1333 $field->setAttribute('data-entity-value', json_encode($data));
1340 * Convert all date fields within the params to mysql date ready for the
1341 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1342 * and if time is defined it is incorporated
1344 * @param array $params input params from the form
1346 * @todo it would probably be better to work on $this->_params than a passed array
1347 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1350 function convertDateFieldsToMySQL(&$params){
1351 foreach ($this->_dateFields
as $fieldName => $specs){
1352 if(!empty($params[$fieldName])){
1353 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1354 CRM_Utils_Date
::processDate(
1355 $params[$fieldName],
1356 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1360 if(isset($specs['default'])){
1361 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1367 function removeFileRequiredRules($elementName) {
1368 $this->_required
= array_diff($this->_required
, array($elementName));
1369 if (isset($this->_rules
[$elementName])) {
1370 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1371 if ($ruleInfo['type'] == 'uploadedfile') {
1372 unset($this->_rules
[$elementName][$index]);
1375 if (empty($this->_rules
[$elementName])) {
1376 unset($this->_rules
[$elementName]);
1382 * Function that can be defined in Form to override or
1383 * perform specific action on cancel action
1387 function cancelAction() {}
1390 * Helper function to verify that required fields have been filled
1391 * Typically called within the scope of a FormRule function
1393 static function validateMandatoryFields($fields, $values, &$errors) {
1394 foreach ($fields as $name => $fld) {
1395 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1396 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1402 * Get contact if for a form object. Prioritise
1403 * - cid in URL if 0 (on behalf on someoneelse)
1404 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1405 * - logged in user id if it matches the one in the cid in the URL
1406 * - contact id validated from a checksum from a checksum
1407 * - cid from the url if the caller has ACL permission to view
1408 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1410 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1412 function getContactID() {
1413 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1414 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1415 $tempID = $this->_params
['select_contact_id'];
1417 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1418 // event form stores as an indexed array, contribution form not so much...
1419 $tempID = $this->_params
[0]['select_contact_id'];
1422 // force to ignore the authenticated user
1423 if ($tempID === '0' ||
$tempID === 0) {
1424 // we set the cid on the form so that this will be retained for the Confirm page
1425 // in the multi-page form & prevent us returning the $userID when this is called
1427 // we don't really need to set it when $tempID is set because the params have that stored
1428 $this->set('cid', 0);
1432 $userID = $this->getLoggedInUserContactID();
1434 if ($tempID == $userID) {
1438 //check if this is a checksum authentication
1439 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1440 if ($userChecksum) {
1441 //check for anonymous user.
1442 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1447 // check if user has permission, CRM-12062
1448 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1456 * Get the contact id of the logged in user
1458 function getLoggedInUserContactID() {
1459 // check if the user is logged in and has a contact ID
1460 $session = CRM_Core_Session
::singleton();
1461 return $session->get('userID');
1465 * add autoselector field -if user has permission to view contacts
1466 * If adding this to a form you also need to add to the tpl e.g
1468 * {if !empty($selectable)}
1469 * <div class="crm-summary-row">
1470 * <div class="crm-label">{$form.select_contact.label}</div>
1471 * <div class="crm-content">
1472 * {$form.select_contact.html}
1476 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1477 * @param array $field metadata of field to use as selector including
1480 * - url (for ajax lookup)
1482 * @todo add data attributes so we can deal with multiple instances on a form
1484 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1485 $autoCompleteField = array_merge(array(
1486 'name_field' => 'select_contact',
1487 'id_field' => 'select_contact_id',
1488 'field_text' => ts('Select Contact'),
1489 'show_hide' => TRUE,
1490 'show_text' => ts('to select someone already in our database.'),
1491 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
1492 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
1493 'max' => civicrm_api3('setting', 'getvalue', array(
1494 'name' => 'search_autocomplete_count',
1495 'group' => 'Search Preferences',
1497 ), $autoCompleteField);
1499 if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
1500 $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
1501 $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
1502 $this->assign('selectable', $autoCompleteField['id_field']);
1504 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
1506 'form' => array('autocompletes' => $autoCompleteField),
1507 'ids' => array('profile' => $profiles),
1513 * Add the options appropriate to cid = zero - ie. autocomplete
1515 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1516 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1517 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1518 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1520 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1521 $this->assign('nocid', TRUE);
1522 $profiles = array();
1523 if($this->_values
['custom_pre_id']) {
1524 $profiles[] = $this->_values
['custom_pre_id'];
1526 if($this->_values
['custom_post_id']) {
1527 $profiles[] = $this->_values
['custom_post_id'];
1529 if($onlinePaymentProcessorEnabled) {
1530 $profiles[] = 'billing';
1532 if(!empty($this->_values
)) {
1533 $this->addAutoSelector($profiles);
1538 * Set default values on form for given contact (or no contact defaults)
1539 * @param mixed $profile_id (can be id, or profile name)
1540 * @param integer $contactID
1542 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1544 $defaults = civicrm_api3('profile', 'getsingle', array(
1545 'profile_id' => (array) $profile_id,
1546 'contact_id' => $contactID,
1550 catch (Exception
$e) {
1551 // the try catch block gives us silent failure -not 100% sure this is a good idea
1552 // as silent failures are often worse than noisy ones