3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
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 * Constructor for the basic form page
151 * We should not use QuickForm directly. This class provides a lot
152 * of default convenient functions, rules and buttons
154 * @param object $state State associated with this form
155 * @param \const|\enum|int $action The mode the form is operating in (None/Create/View/Update/Delete)
156 * @param string $method The type of http method used (GET/POST)
157 * @param string $name The name of the form if different from class name
159 * @return \CRM_Core_Form
162 function __construct(
164 $action = CRM_Core_Action
::NONE
,
170 $this->_name
= $name;
173 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
176 $this->HTML_QuickForm_Page($this->_name
, $method);
178 $this->_state
=& $state;
180 $this->_state
->setName($this->_name
);
182 $this->_action
= (int) $action;
184 $this->registerRules();
186 // let the constructor initialize this, should happen only once
187 if (!isset(self
::$_template)) {
188 self
::$_template = CRM_Core_Smarty
::singleton();
191 $this->assign('snippet', CRM_Utils_Array
::value('snippet', $_GET));
194 static function generateID() {
198 * register all the standard rules that most forms potentially use
204 function registerRules() {
205 static $rules = array(
206 'title', 'longTitle', 'variable', 'qfVariable',
207 'phone', 'integer', 'query',
209 'domain', 'numberOfDigit',
210 'date', 'currentDate',
211 'asciiFile', 'htmlFile', 'utf8File',
212 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
213 'xssString', 'fileExists', 'autocomplete', 'validContact',
216 foreach ($rules as $rule) {
217 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
222 * Simple easy to use wrapper around addElement. Deal with
223 * simple validation rules
227 * @param string $label
228 * @param string $attributes
229 * @param bool $required
232 * @internal param \type $string of html element to be added
233 * @internal param \name $string of the html element
234 * @internal param \display $string label for the html element
235 * @internal param \attributes $string used for this element.
236 * These are not default values
237 * @internal param \is $bool this a required field
239 * @return HTML_QuickForm_Element could be an error object
242 function &add($type, $name, $label = '',
243 $attributes = '', $required = FALSE, $extra = NULL
245 // Normalize this property
246 if ($type == 'select' && is_array($extra) && !empty($extra['multiple'])) {
247 $extra['multiple'] = 'multiple';
249 $element = $this->addElement($type, $name, $label, $attributes, $extra);
250 if (HTML_QuickForm
::isError($element)) {
251 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
255 if ($type == 'file') {
256 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
259 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
261 if (HTML_QuickForm
::isError($error)) {
262 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
270 * This function is called before buildForm. Any pre-processing that
271 * needs to be done for buildForm should be done here
273 * This is a virtual function and should be redefined if needed
280 function preProcess() {}
283 * This function is called after the form is validated. Any
284 * processing of form state etc should be done in this function.
285 * Typically all processing associated with a form should be done
286 * here and relevant state should be stored in the session
288 * This is a virtual function and should be redefined if needed
295 function postProcess() {}
298 * This function is just a wrapper, so that we can call all the hook functions
299 * @param bool $allowAjax - FIXME: This feels kind of hackish, ideally we would take the json-related code from this function
300 * and bury it deeper down in the controller
302 function mainProcess($allowAjax = TRUE) {
303 $this->postProcess();
304 $this->postProcessHook();
306 // Respond with JSON if in AJAX context (also support legacy value '6')
307 if ($allowAjax && !empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
308 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
309 $this->ajaxResponse
['action'] = $this->_action
;
310 if (isset($this->_id
) ||
isset($this->id
)) {
311 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
313 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
318 * The postProcess hook is typically called by the framework
319 * However in a few cases, the form exits or redirects early in which
320 * case it needs to call this function so other modules can do the needful
321 * Calling this function directly should be avoided if possible. In general a
322 * better way is to do setUserContext so the framework does the redirect
325 function postProcessHook() {
326 CRM_Utils_Hook
::postProcess(get_class($this), $this);
330 * This virtual function is used to build the form. It replaces the
331 * buildForm associated with QuickForm_Page. This allows us to put
332 * preProcess in front of the actual form building routine
339 function buildQuickForm() {}
342 * This virtual function is used to set the default values of
343 * various form elements
347 * @return array reference to the array of default values
350 function setDefaultValues() {}
353 * This is a virtual function that adds group and global rules to
354 * the form. Keeping it distinct from the form to keep code small
355 * and localized in the form building code
362 function addRules() {}
365 * Performs the server side validation
368 * @return boolean true if no error found
369 * @throws HTML_QuickForm_Error
371 function validate() {
372 $error = parent
::validate();
374 $hookErrors = CRM_Utils_Hook
::validate(
376 $this->_submitValues
,
381 if (!is_array($hookErrors)) {
382 $hookErrors = array();
385 CRM_Utils_Hook
::validateForm(
387 $this->_submitValues
,
393 if (!empty($hookErrors)) {
394 $this->_errors +
= $hookErrors;
397 return (0 == count($this->_errors
));
401 * Core function that builds the form. We redefine this function
402 * here and expect all CRM forms to build their form in the function
406 function buildForm() {
407 $this->_formBuilt
= TRUE;
411 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
414 $this->controller
->_key
&&
415 $this->controller
->_generateQFKey
417 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
418 $this->assign('qfKey', $this->controller
->_key
);
422 // _generateQFKey suppresses the qfKey generation on form snippets that
423 // are part of other forms, hence we use that to avoid adding entryURL
424 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
425 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
428 $this->buildQuickForm();
430 $defaults = $this->setDefaultValues();
431 unset($defaults['qfKey']);
433 if (!empty($defaults)) {
434 $this->setDefaults($defaults);
437 // call the form hook
438 // also call the hook function so any modules can set thier own custom defaults
439 // the user can do both the form and set default values with this hook
440 CRM_Utils_Hook
::buildForm(get_class($this), $this);
444 //Set html data-attribute to enable warning user of unsaved changes
445 if ($this->unsavedChangesWarn
=== true
446 ||
(!isset($this->unsavedChangesWarn
)
447 && ($this->_action
& CRM_Core_Action
::ADD ||
$this->_action
& CRM_Core_Action
::UPDATE
)
450 $this->setAttribute('data-warn-changes', 'true');
455 * Add default Next / Back buttons
457 * @param array array of associative arrays in the order in which the buttons should be
458 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
459 * The base form class will define a bunch of static arrays for commonly used
467 function addButtons($params) {
470 foreach ($params as $button) {
471 $js = CRM_Utils_Array
::value('js', $button);
472 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
474 $attrs = array('class' => 'crm-form-submit default');
477 $attrs = array('class' => 'crm-form-submit');
481 $attrs = array_merge($js, $attrs);
484 if ($button['type'] === 'cancel') {
485 $attrs['class'] .= ' cancel';
488 if ($button['type'] === 'reset') {
489 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
492 if (!empty($button['subName'])) {
493 $buttonName = $this->getButtonName($button['type'], $button['subName']);
496 $buttonName = $this->getButtonName($button['type']);
499 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
500 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
502 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
504 if (!empty($button['isDefault'])) {
505 $this->setDefaultAction($button['type']);
508 // if button type is upload, set the enctype
509 if ($button['type'] == 'upload') {
510 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
511 $this->setMaxFileSize();
514 // hack - addGroup uses an array to express variable spacing, read from the last element
515 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
517 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
521 * getter function for Name
531 * getter function for State
536 function &getState() {
537 return $this->_state
;
541 * getter function for StateType
546 function getStateType() {
547 return $this->_state
->getType();
551 * getter function for title. Should be over-ridden by derived class
556 function getTitle() {
557 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
561 * setter function for title.
563 * @param string $title the title of the form
568 function setTitle($title) {
569 $this->_title
= $title;
573 * Setter function for options
580 function setOptions($options) {
581 $this->_options
= $options;
585 * getter function for link.
591 $config = CRM_Core_Config
::singleton();
592 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
593 '_qf_' . $this->_name
. '_display=true'
598 * boolean function to determine if this is a one form page
603 function isSimpleForm() {
604 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
608 * getter function for Form Action
613 function getFormAction() {
614 return $this->_attributes
['action'];
618 * setter function for Form Action
625 function setFormAction($action) {
626 $this->_attributes
['action'] = $action;
630 * render form and return contents
635 function toSmarty() {
636 $renderer = $this->getRenderer();
637 $this->accept($renderer);
638 $content = $renderer->toArray();
639 $content['formName'] = $this->getName();
644 * getter function for renderer. If renderer is not set
645 * create one and initialize it
650 function &getRenderer() {
651 if (!isset($this->_renderer
)) {
652 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
654 return $this->_renderer
;
658 * Use the form name to create the tpl file name
663 function getTemplateFileName() {
664 $ext = CRM_Extension_System
::singleton()->getMapper();
665 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
666 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
667 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
670 $tplname = str_replace('_',
672 CRM_Utils_System
::getClassName($this)
679 * A wrapper for getTemplateFileName that includes calling the hook to
680 * prevent us from having to copy & paste the logic of calling the hook
682 function getHookedTemplateFileName() {
683 $pageTemplateFile = $this->getTemplateFileName();
684 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
685 return $pageTemplateFile;
689 * Default extra tpl file basically just replaces .tpl with .extra.tpl
690 * i.e. we dont override
695 function overrideExtraTemplateFileName() {
700 * Error reporting mechanism
702 * @param string $message Error Message
703 * @param int $code Error Code
704 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
709 function error($message, $code = NULL, $dao = NULL) {
711 $dao->query('ROLLBACK');
714 $error = CRM_Core_Error
::singleton();
716 $error->push($code, $message);
720 * Store the variable with the value in the form scope
722 * @param string name : name of the variable
723 * @param mixed value : value of the variable
730 function set($name, $value) {
731 $this->controller
->set($name, $value);
735 * Get the variable from the form scope
737 * @param string name : name of the variable
744 function get($name) {
745 return $this->controller
->get($name);
754 function getAction() {
755 return $this->_action
;
761 * @param int $action the mode we want to set the form
766 function setAction($action) {
767 $this->_action
= $action;
771 * assign value to name in template
774 * @param mixed $value value of varaible
776 * @internal param array|string $name name of variable
780 function assign($var, $value = NULL) {
781 self
::$_template->assign($var, $value);
785 * assign value to name in template by reference
788 * @param mixed $value value of varaible
790 * @internal param array|string $name name of variable
794 function assign_by_ref($var, &$value) {
795 self
::$_template->assign_by_ref($var, $value);
799 * appends values to template variables
801 * @param array|string $tpl_var the template variable name(s)
802 * @param mixed $value the value to append
805 function append($tpl_var, $value=NULL, $merge=FALSE) {
806 self
::$_template->append($tpl_var, $value, $merge);
810 * Returns an array containing template variables
812 * @param string $name
814 * @internal param string $type
817 function get_template_vars($name=null) {
818 return self
::$_template->get_template_vars($name);
825 * @param array $attributes
826 * @param null $separator
827 * @param bool $required
829 * @return HTML_QuickForm_group
831 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
833 $attributes = $attributes ?
$attributes : array();
834 $allowClear = !empty($attributes['allowClear']);
835 unset($attributes['allowClear']);
836 $attributes +
= array('id_suffix' => $name);
837 foreach ($values as $key => $var) {
838 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
840 $group = $this->addGroup($options, $name, $title, $separator);
842 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
845 $group->setAttribute('allowClear', TRUE);
853 * @param bool $allowClear
854 * @param null $required
855 * @param array $attributes
857 function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
858 $attributes +
= array('id_suffix' => $id);
860 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
861 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
863 $group = $this->addGroup($choice, $id, $title);
865 $group->setAttribute('allowClear', TRUE);
868 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
877 * @param null $attributes
878 * @param null $required
879 * @param null $javascriptMethod
880 * @param string $separator
881 * @param bool $flipValues
883 function addCheckBox($id, $title, $values, $other = NULL,
884 $attributes = NULL, $required = NULL,
885 $javascriptMethod = NULL,
886 $separator = '<br />', $flipValues = FALSE
890 if ($javascriptMethod) {
891 foreach ($values as $key => $var) {
893 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
896 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
901 foreach ($values as $key => $var) {
903 $options[] = $this->createElement('checkbox', $var, NULL, $key);
906 $options[] = $this->createElement('checkbox', $key, NULL, $var);
911 $this->addGroup($options, $id, $title, $separator);
914 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
919 ts('%1 is a required field.', array(1 => $title)),
925 function resetValues() {
926 $data = $this->controller
->container();
927 $data['values'][$this->_name
] = array();
931 * simple shell that derived classes can call to add buttons to
932 * the form with a customized title for the main Submit
934 * @param string $title title of the main button
935 * @param string $nextType
936 * @param string $backType
937 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
939 * @internal param string $type button type for the form after processing
943 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
945 if ($backType != NULL) {
948 'name' => ts('Previous'),
951 if ($nextType != NULL) {
958 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
960 $buttons[] = $nextButton;
962 $this->addButtons($buttons);
967 * @param string $from
969 * @param string $label
970 * @param string $dateFormat
971 * @param bool $required
972 * @param bool $displayTime
974 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
976 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
977 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
979 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
980 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
985 * Adds a select based on field metadata
986 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
987 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
988 * @param $name - field name to go on the form
989 * @param array $props - mix of html attributes and special properties, namely
990 * - entity (api entity name, can usually be inferred automatically from the form class)
991 * - field (field name - only needed if different from name used on the form)
992 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
993 * - placeholder - set to NULL to disable
995 * @param bool $required
996 * @throws CRM_Core_Exception
997 * @return HTML_QuickForm_Element
999 function addSelect($name, $props = array(), $required = FALSE) {
1000 if (!isset($props['entity'])) {
1001 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
1003 if (!isset($props['field'])) {
1004 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
1006 $info = civicrm_api3($props['entity'], 'getoptions', array(
1007 'field' => $props['field'],
1008 'options' => array('metadata' => array('fields'))
1011 $options = $info['values'];
1012 if (!array_key_exists('placeholder', $props)) {
1013 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
1015 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
1016 $options = array('' => '') +
$options;
1018 // Handle custom field
1019 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1020 list(, $id) = explode('_', $name);
1021 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
1022 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
1023 $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);
1027 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
1029 $uniqueName === $props['field'] ||
1030 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
1031 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
1036 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
1037 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
1039 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
1040 $props['data-api-entity'] = $props['entity'];
1041 $props['data-api-field'] = $props['field'];
1042 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
1043 return $this->add('select', $name, $label, $options, $required, $props);
1047 * Add a widget for selecting/editing/creating/copying a profile form
1049 * @param string $name HTML form-element name
1050 * @param string $label Printable label
1051 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
1052 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
1053 * @param array $entities
1055 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
1057 // FIXME: Instead of adhoc serialization, use a single json_encode()
1058 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
1059 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1060 $this->add('text', $name, $label, array(
1061 'class' => 'crm-profile-selector',
1062 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1063 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1064 'data-entities' => json_encode($entities),
1071 * @param $attributes
1072 * @param bool $forceTextarea
1074 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1075 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1076 // 2. Based on the option, initialise proper editor
1077 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1080 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1081 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1083 if (!$editor ||
$forceTextarea) {
1084 $editor = 'textarea';
1086 if ($editor == 'joomla default editor') {
1087 $editor = 'joomlaeditor';
1090 if ($editor == 'drupal default editor') {
1091 $editor = 'drupalwysiwyg';
1094 //lets add the editor as a attribute
1095 $attributes['editor'] = $editor;
1097 $this->addElement($editor, $name, $label, $attributes);
1098 $this->assign('editor', $editor);
1100 // include wysiwyg editor js files
1101 // FIXME: This code does not make any sense
1102 $includeWysiwygEditor = FALSE;
1103 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1104 if (!$includeWysiwygEditor) {
1105 $includeWysiwygEditor = TRUE;
1106 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1109 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1115 * @param null $required
1116 * @param null $extra
1118 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1119 $this->addElement('select', $id, $title,
1121 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1124 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1132 * @param $attributes
1133 * @param null $required
1134 * @param null $javascriptMethod
1136 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1138 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1141 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1148 public function getRootTitle() {
1155 public function getCompleteTitle() {
1156 return $this->getRootTitle() . $this->getTitle();
1160 * @return CRM_Core_Smarty
1162 static function &getTemplate() {
1163 return self
::$_template;
1167 * @param $elementName
1169 function addUploadElement($elementName) {
1170 $uploadNames = $this->get('uploadNames');
1171 if (!$uploadNames) {
1172 $uploadNames = array();
1174 if (is_array($elementName)) {
1175 foreach ($elementName as $name) {
1176 if (!in_array($name, $uploadNames)) {
1177 $uploadNames[] = $name;
1182 if (!in_array($elementName, $uploadNames)) {
1183 $uploadNames[] = $elementName;
1186 $this->set('uploadNames', $uploadNames);
1188 $config = CRM_Core_Config
::singleton();
1189 if (!empty($uploadNames)) {
1190 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1197 function buttonType() {
1198 $uploadNames = $this->get('uploadNames');
1199 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1200 $this->assign('buttonType', $buttonType);
1209 function getVar($name) {
1210 return isset($this->$name) ?
$this->$name : NULL;
1217 function setVar($name, $value) {
1218 $this->$name = $value;
1222 * Function to add date
1223 * @param string $name name of the element
1224 * @param string $label label of the element
1225 * @param array $attributes key / value pair
1228 * $attributes = array ( 'addTime' => true,
1229 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1231 * @param boolean $required true if required
1234 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1235 if (!empty($attributes['formatType'])) {
1236 // get actual format
1237 $params = array('name' => $attributes['formatType']);
1240 // cache date information
1242 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1243 if (empty($dateFormat[$key])) {
1244 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1245 $dateFormat[$key] = $values;
1248 $values = $dateFormat[$key];
1251 if ($values['date_format']) {
1252 $attributes['format'] = $values['date_format'];
1255 if (!empty($values['time_format'])) {
1256 $attributes['timeFormat'] = $values['time_format'];
1258 $attributes['startOffset'] = $values['start'];
1259 $attributes['endOffset'] = $values['end'];
1262 $config = CRM_Core_Config
::singleton();
1263 if (empty($attributes['format'])) {
1264 $attributes['format'] = $config->dateInputFormat
;
1267 if (!isset($attributes['startOffset'])) {
1268 $attributes['startOffset'] = 10;
1271 if (!isset($attributes['endOffset'])) {
1272 $attributes['endOffset'] = 10;
1275 $this->add('text', $name, $label, $attributes);
1277 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1279 if (!isset($attributes['timeFormat'])) {
1280 $timeFormat = $config->timeInputFormat
;
1283 $timeFormat = $attributes['timeFormat'];
1286 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1288 $show24Hours = TRUE;
1289 if ($timeFormat == 1) {
1290 $show24Hours = FALSE;
1293 //CRM-6664 -we are having time element name
1294 //in either flat string or an array format.
1295 $elementName = $name . '_time';
1296 if (substr($name, -1) == ']') {
1297 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1300 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1305 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1306 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1307 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1313 * Function that will add date and time
1315 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1316 $addTime = array('addTime' => TRUE);
1317 if (is_array($attributes)) {
1318 $attributes = array_merge($attributes, $addTime);
1321 $attributes = $addTime;
1324 $this->addDate($name, $label, $required, $attributes);
1328 * add a currency and money element to the form
1330 function addMoney($name,
1334 $addCurrency = TRUE,
1335 $currencyName = 'currency',
1336 $defaultCurrency = NULL,
1337 $freezeCurrency = FALSE
1339 $element = $this->add('text', $name, $label, $attributes, $required);
1340 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1343 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1350 * add currency element to the form
1352 function addCurrency($name = 'currency',
1355 $defaultCurrency = NULL,
1356 $freezeCurrency = FALSE
1358 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1359 $options = array('class' => 'crm-select2 eight');
1361 $currencies = array('' => '') +
$currencies;
1362 $options['placeholder'] = ts('- none -');
1364 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1365 if ($freezeCurrency) {
1368 if (!$defaultCurrency) {
1369 $config = CRM_Core_Config
::singleton();
1370 $defaultCurrency = $config->defaultCurrency
;
1372 $this->setDefaults(array($name => $defaultCurrency));
1376 * Create a single or multiple entity ref field
1377 * @param string $name
1378 * @param string $label
1379 * @param array $props mix of html and widget properties, including:
1380 * - select - params to give to select2 widget
1381 * - entity - defaults to contact
1382 * - create - can the user create a new entity on-the-fly?
1383 * Set to TRUE if entity is contact and you want the default profiles,
1384 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1385 * note that permissions are checked automatically
1386 * - api - array of settings for the getlist api wrapper
1387 * note that it accepts a 'params' setting which will be passed to the underlying api
1388 * - placeholder - string
1390 * - class, etc. - other html properties
1391 * @param bool $required
1394 * @return HTML_QuickForm_Element
1396 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1397 require_once "api/api.php";
1398 $config = CRM_Core_Config
::singleton();
1399 // Default properties
1400 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1401 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1402 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1404 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1405 unset($props['create']);
1408 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1410 $defaults = array();
1411 if (!empty($props['multiple'])) {
1412 $defaults['multiple'] = TRUE;
1414 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1416 $this->formatReferenceFieldAttributes($props);
1417 return $this->add('text', $name, $label, $props, $required);
1423 private function formatReferenceFieldAttributes(&$props) {
1424 $props['data-select-params'] = json_encode($props['select']);
1425 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1426 $props['data-api-entity'] = $props['entity'];
1427 if (!empty($props['create'])) {
1428 $props['data-create-links'] = json_encode($props['create']);
1430 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1434 * Convert all date fields within the params to mysql date ready for the
1435 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1436 * and if time is defined it is incorporated
1438 * @param array $params input params from the form
1440 * @todo it would probably be better to work on $this->_params than a passed array
1441 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1444 function convertDateFieldsToMySQL(&$params){
1445 foreach ($this->_dateFields
as $fieldName => $specs){
1446 if(!empty($params[$fieldName])){
1447 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1448 CRM_Utils_Date
::processDate(
1449 $params[$fieldName],
1450 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1454 if(isset($specs['default'])){
1455 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1462 * @param $elementName
1464 function removeFileRequiredRules($elementName) {
1465 $this->_required
= array_diff($this->_required
, array($elementName));
1466 if (isset($this->_rules
[$elementName])) {
1467 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1468 if ($ruleInfo['type'] == 'uploadedfile') {
1469 unset($this->_rules
[$elementName][$index]);
1472 if (empty($this->_rules
[$elementName])) {
1473 unset($this->_rules
[$elementName]);
1479 * Function that can be defined in Form to override or
1480 * perform specific action on cancel action
1484 function cancelAction() {}
1487 * Helper function to verify that required fields have been filled
1488 * Typically called within the scope of a FormRule function
1490 static function validateMandatoryFields($fields, $values, &$errors) {
1491 foreach ($fields as $name => $fld) {
1492 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1493 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1499 * Get contact if for a form object. Prioritise
1500 * - cid in URL if 0 (on behalf on someoneelse)
1501 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1502 * - logged in user id if it matches the one in the cid in the URL
1503 * - contact id validated from a checksum from a checksum
1504 * - cid from the url if the caller has ACL permission to view
1505 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1507 * @return mixed NULL|integer
1509 function getContactID() {
1510 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1511 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1512 $tempID = $this->_params
['select_contact_id'];
1514 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1515 // event form stores as an indexed array, contribution form not so much...
1516 $tempID = $this->_params
[0]['select_contact_id'];
1519 // force to ignore the authenticated user
1520 if ($tempID === '0' ||
$tempID === 0) {
1521 // we set the cid on the form so that this will be retained for the Confirm page
1522 // in the multi-page form & prevent us returning the $userID when this is called
1524 // we don't really need to set it when $tempID is set because the params have that stored
1525 $this->set('cid', 0);
1529 $userID = $this->getLoggedInUserContactID();
1531 if ($tempID == $userID) {
1535 //check if this is a checksum authentication
1536 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1537 if ($userChecksum) {
1538 //check for anonymous user.
1539 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1544 // check if user has permission, CRM-12062
1545 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1553 * Get the contact id of the logged in user
1555 function getLoggedInUserContactID() {
1556 // check if the user is logged in and has a contact ID
1557 $session = CRM_Core_Session
::singleton();
1558 return $session->get('userID');
1562 * add autoselector field -if user has permission to view contacts
1563 * If adding this to a form you also need to add to the tpl e.g
1565 * {if !empty($selectable)}
1566 * <div class="crm-summary-row">
1567 * <div class="crm-label">{$form.select_contact.label}</div>
1568 * <div class="crm-content">
1569 * {$form.select_contact.html}
1574 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1575 * @param array $autoCompleteField
1577 * @internal param array $field metadata of field to use as selector including
1580 * - url (for ajax lookup)
1582 * @todo add data attributes so we can deal with multiple instances on a form
1584 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1585 $autoCompleteField = array_merge(array(
1586 'id_field' => 'select_contact_id',
1587 'placeholder' => ts('Select someone else ...'),
1588 'show_hide' => TRUE,
1589 'api' => array('params' => array('contact_type' => 'Individual'))
1590 ), $autoCompleteField);
1592 if($this->canUseAjaxContactLookups()) {
1593 $this->assign('selectable', $autoCompleteField['id_field']);
1594 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1596 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1598 'form' => array('autocompletes' => $autoCompleteField),
1599 'ids' => array('profile' => $profiles),
1607 function canUseAjaxContactLookups() {
1608 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1609 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1615 * Add the options appropriate to cid = zero - ie. autocomplete
1617 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1618 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1619 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1620 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1622 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1623 $this->assign('nocid', TRUE);
1624 $profiles = array();
1625 if($this->_values
['custom_pre_id']) {
1626 $profiles[] = $this->_values
['custom_pre_id'];
1628 if($this->_values
['custom_post_id']) {
1629 $profiles[] = $this->_values
['custom_post_id'];
1631 if($onlinePaymentProcessorEnabled) {
1632 $profiles[] = 'billing';
1634 if(!empty($this->_values
)) {
1635 $this->addAutoSelector($profiles);
1640 * Set default values on form for given contact (or no contact defaults)
1642 * @param mixed $profile_id (can be id, or profile name)
1643 * @param integer $contactID
1647 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1649 $defaults = civicrm_api3('profile', 'getsingle', array(
1650 'profile_id' => (array) $profile_id,
1651 'contact_id' => $contactID,
1655 catch (Exception
$e) {
1656 // the try catch block gives us silent failure -not 100% sure this is a good idea
1657 // as silent failures are often worse than noisy ones
1663 * Sets form attribute
1666 function preventAjaxSubmit() {
1667 $this->setAttribute('data-no-ajax-submit', 'true');
1671 * Sets form attribute
1674 function allowAjaxSubmit() {
1675 $this->removeAttribute('data-no-ajax-submit');