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
300 function mainProcess() {
301 $this->postProcess();
302 $this->postProcessHook();
304 // Respond with JSON if in AJAX context (also support legacy value '6')
305 if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
306 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
307 $this->ajaxResponse
['action'] = $this->_action
;
308 if (isset($this->_id
) ||
isset($this->id
)) {
309 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
311 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
316 * The postProcess hook is typically called by the framework
317 * However in a few cases, the form exits or redirects early in which
318 * case it needs to call this function so other modules can do the needful
319 * Calling this function directly should be avoided if possible. In general a
320 * better way is to do setUserContext so the framework does the redirect
323 function postProcessHook() {
324 CRM_Utils_Hook
::postProcess(get_class($this), $this);
328 * This virtual function is used to build the form. It replaces the
329 * buildForm associated with QuickForm_Page. This allows us to put
330 * preProcess in front of the actual form building routine
337 function buildQuickForm() {}
340 * This virtual function is used to set the default values of
341 * various form elements
345 * @return array reference to the array of default values
348 function setDefaultValues() {}
351 * This is a virtual function that adds group and global rules to
352 * the form. Keeping it distinct from the form to keep code small
353 * and localized in the form building code
360 function addRules() {}
363 * Performs the server side validation
366 * @return boolean true if no error found
367 * @throws HTML_QuickForm_Error
369 function validate() {
370 $error = parent
::validate();
372 $hookErrors = CRM_Utils_Hook
::validate(
374 $this->_submitValues
,
379 if (!is_array($hookErrors)) {
380 $hookErrors = array();
383 CRM_Utils_Hook
::validateForm(
385 $this->_submitValues
,
391 if (!empty($hookErrors)) {
392 $this->_errors +
= $hookErrors;
395 return (0 == count($this->_errors
));
399 * Core function that builds the form. We redefine this function
400 * here and expect all CRM forms to build their form in the function
404 function buildForm() {
405 $this->_formBuilt
= TRUE;
409 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
412 $this->controller
->_key
&&
413 $this->controller
->_generateQFKey
415 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
416 $this->assign('qfKey', $this->controller
->_key
);
420 // _generateQFKey suppresses the qfKey generation on form snippets that
421 // are part of other forms, hence we use that to avoid adding entryURL
422 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
423 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
426 $this->buildQuickForm();
428 $defaults = $this->setDefaultValues();
429 unset($defaults['qfKey']);
431 if (!empty($defaults)) {
432 $this->setDefaults($defaults);
435 // call the form hook
436 // also call the hook function so any modules can set thier own custom defaults
437 // the user can do both the form and set default values with this hook
438 CRM_Utils_Hook
::buildForm(get_class($this), $this);
442 //Set html data-attribute to enable warning user of unsaved changes
443 if ($this->unsavedChangesWarn
=== true
444 ||
(!isset($this->unsavedChangesWarn
)
445 && ($this->_action
& CRM_Core_Action
::ADD ||
$this->_action
& CRM_Core_Action
::UPDATE
)
448 $this->setAttribute('data-warn-changes', 'true');
453 * Add default Next / Back buttons
455 * @param array array of associative arrays in the order in which the buttons should be
456 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
457 * The base form class will define a bunch of static arrays for commonly used
465 function addButtons($params) {
468 foreach ($params as $button) {
469 $js = CRM_Utils_Array
::value('js', $button);
470 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
472 $attrs = array('class' => 'form-submit default');
475 $attrs = array('class' => 'form-submit');
479 $attrs = array_merge($js, $attrs);
482 if ($button['type'] === 'cancel') {
483 $attrs['class'] .= ' cancel';
486 if ($button['type'] === 'reset') {
487 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
490 if (!empty($button['subName'])) {
491 $buttonName = $this->getButtonName($button['type'], $button['subName']);
494 $buttonName = $this->getButtonName($button['type']);
497 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
498 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
500 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
502 if (!empty($button['isDefault'])) {
503 $this->setDefaultAction($button['type']);
506 // if button type is upload, set the enctype
507 if ($button['type'] == 'upload') {
508 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
509 $this->setMaxFileSize();
512 // hack - addGroup uses an array to express variable spacing, read from the last element
513 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
515 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
519 * getter function for Name
529 * getter function for State
534 function &getState() {
535 return $this->_state
;
539 * getter function for StateType
544 function getStateType() {
545 return $this->_state
->getType();
549 * getter function for title. Should be over-ridden by derived class
554 function getTitle() {
555 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
559 * setter function for title.
561 * @param string $title the title of the form
566 function setTitle($title) {
567 $this->_title
= $title;
571 * Setter function for options
578 function setOptions($options) {
579 $this->_options
= $options;
583 * getter function for link.
589 $config = CRM_Core_Config
::singleton();
590 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
591 '_qf_' . $this->_name
. '_display=true'
596 * boolean function to determine if this is a one form page
601 function isSimpleForm() {
602 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
606 * getter function for Form Action
611 function getFormAction() {
612 return $this->_attributes
['action'];
616 * setter function for Form Action
623 function setFormAction($action) {
624 $this->_attributes
['action'] = $action;
628 * render form and return contents
633 function toSmarty() {
634 $renderer = $this->getRenderer();
635 $this->accept($renderer);
636 $content = $renderer->toArray();
637 $content['formName'] = $this->getName();
642 * getter function for renderer. If renderer is not set
643 * create one and initialize it
648 function &getRenderer() {
649 if (!isset($this->_renderer
)) {
650 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
652 return $this->_renderer
;
656 * Use the form name to create the tpl file name
661 function getTemplateFileName() {
662 $ext = CRM_Extension_System
::singleton()->getMapper();
663 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
664 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
665 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
668 $tplname = str_replace('_',
670 CRM_Utils_System
::getClassName($this)
677 * A wrapper for getTemplateFileName that includes calling the hook to
678 * prevent us from having to copy & paste the logic of calling the hook
680 function getHookedTemplateFileName() {
681 $pageTemplateFile = $this->getTemplateFileName();
682 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
683 return $pageTemplateFile;
687 * Default extra tpl file basically just replaces .tpl with .extra.tpl
688 * i.e. we dont override
693 function overrideExtraTemplateFileName() {
698 * Error reporting mechanism
700 * @param string $message Error Message
701 * @param int $code Error Code
702 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
707 function error($message, $code = NULL, $dao = NULL) {
709 $dao->query('ROLLBACK');
712 $error = CRM_Core_Error
::singleton();
714 $error->push($code, $message);
718 * Store the variable with the value in the form scope
720 * @param string name : name of the variable
721 * @param mixed value : value of the variable
728 function set($name, $value) {
729 $this->controller
->set($name, $value);
733 * Get the variable from the form scope
735 * @param string name : name of the variable
742 function get($name) {
743 return $this->controller
->get($name);
752 function getAction() {
753 return $this->_action
;
759 * @param int $action the mode we want to set the form
764 function setAction($action) {
765 $this->_action
= $action;
769 * assign value to name in template
772 * @param mixed $value value of varaible
774 * @internal param array|string $name name of variable
778 function assign($var, $value = NULL) {
779 self
::$_template->assign($var, $value);
783 * assign value to name in template by reference
786 * @param mixed $value value of varaible
788 * @internal param array|string $name name of variable
792 function assign_by_ref($var, &$value) {
793 self
::$_template->assign_by_ref($var, $value);
797 * appends values to template variables
799 * @param array|string $tpl_var the template variable name(s)
800 * @param mixed $value the value to append
803 function append($tpl_var, $value=NULL, $merge=FALSE) {
804 self
::$_template->append($tpl_var, $value, $merge);
808 * Returns an array containing template variables
810 * @param string $name
812 * @internal param string $type
815 function get_template_vars($name=null) {
816 return self
::$_template->get_template_vars($name);
823 * @param array $attributes
824 * @param null $separator
825 * @param bool $required
827 * @return HTML_QuickForm_group
829 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
831 $attributes = $attributes ?
$attributes : array();
832 $allowClear = !empty($attributes['allowClear']);
833 unset($attributes['allowClear']);
834 $attributes +
= array('id_suffix' => $name);
835 foreach ($values as $key => $var) {
836 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
838 $group = $this->addGroup($options, $name, $title, $separator);
840 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
843 $group->setAttribute('allowClear', TRUE);
851 * @param bool $allowClear
852 * @param null $required
853 * @param array $attributes
855 function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
856 $attributes +
= array('id_suffix' => $id);
858 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
859 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
861 $group = $this->addGroup($choice, $id, $title);
863 $group->setAttribute('allowClear', TRUE);
866 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
875 * @param null $attributes
876 * @param null $required
877 * @param null $javascriptMethod
878 * @param string $separator
879 * @param bool $flipValues
881 function addCheckBox($id, $title, $values, $other = NULL,
882 $attributes = NULL, $required = NULL,
883 $javascriptMethod = NULL,
884 $separator = '<br />', $flipValues = FALSE
888 if ($javascriptMethod) {
889 foreach ($values as $key => $var) {
891 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
894 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
899 foreach ($values as $key => $var) {
901 $options[] = $this->createElement('checkbox', $var, NULL, $key);
904 $options[] = $this->createElement('checkbox', $key, NULL, $var);
909 $this->addGroup($options, $id, $title, $separator);
912 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
917 ts('%1 is a required field.', array(1 => $title)),
923 function resetValues() {
924 $data = $this->controller
->container();
925 $data['values'][$this->_name
] = array();
929 * simple shell that derived classes can call to add buttons to
930 * the form with a customized title for the main Submit
932 * @param string $title title of the main button
933 * @param string $nextType
934 * @param string $backType
935 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
937 * @internal param string $type button type for the form after processing
941 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
943 if ($backType != NULL) {
946 'name' => ts('Previous'),
949 if ($nextType != NULL) {
956 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
958 $buttons[] = $nextButton;
960 $this->addButtons($buttons);
965 * @param string $from
967 * @param string $label
968 * @param string $dateFormat
969 * @param bool $required
970 * @param bool $displayTime
972 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
974 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
975 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
977 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
978 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
983 * Adds a select based on field metadata
984 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
985 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
986 * @param $name - field name to go on the form
987 * @param array $props - mix of html attributes and special properties, namely
988 * - entity (api entity name, can usually be inferred automatically from the form class)
989 * - field (field name - only needed if different from name used on the form)
990 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
991 * - placeholder - set to NULL to disable
993 * @param bool $required
994 * @throws CRM_Core_Exception
995 * @return HTML_QuickForm_Element
997 function addSelect($name, $props = array(), $required = FALSE) {
998 if (!isset($props['entity'])) {
999 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
1001 if (!isset($props['field'])) {
1002 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
1004 $info = civicrm_api3($props['entity'], 'getoptions', array(
1005 'field' => $props['field'],
1006 'options' => array('metadata' => array('fields'))
1009 $options = $info['values'];
1010 if (!array_key_exists('placeholder', $props)) {
1011 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
1013 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
1014 $options = array('' => '') +
$options;
1016 // Handle custom field
1017 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1018 list(, $id) = explode('_', $name);
1019 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
1020 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
1021 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : 'civicrm/admin/options/' . CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', $gid);
1025 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
1027 $uniqueName === $props['field'] ||
1028 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
1029 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
1034 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
1035 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
1037 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
1038 $props['data-api-entity'] = $props['entity'];
1039 $props['data-api-field'] = $props['field'];
1040 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
1041 return $this->add('select', $name, $label, $options, $required, $props);
1045 * Add a widget for selecting/editing/creating/copying a profile form
1047 * @param string $name HTML form-element name
1048 * @param string $label Printable label
1049 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
1050 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
1051 * @param array $entities
1053 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
1055 // FIXME: Instead of adhoc serialization, use a single json_encode()
1056 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
1057 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1058 $this->add('text', $name, $label, array(
1059 'class' => 'crm-profile-selector',
1060 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1061 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1062 'data-entities' => json_encode($entities),
1069 * @param $attributes
1070 * @param bool $forceTextarea
1072 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1073 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1074 // 2. Based on the option, initialise proper editor
1075 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1078 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1079 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1081 if (!$editor ||
$forceTextarea) {
1082 $editor = 'textarea';
1084 if ($editor == 'joomla default editor') {
1085 $editor = 'joomlaeditor';
1088 if ($editor == 'drupal default editor') {
1089 $editor = 'drupalwysiwyg';
1092 //lets add the editor as a attribute
1093 $attributes['editor'] = $editor;
1095 $this->addElement($editor, $name, $label, $attributes);
1096 $this->assign('editor', $editor);
1098 // include wysiwyg editor js files
1099 // FIXME: This code does not make any sense
1100 $includeWysiwygEditor = FALSE;
1101 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1102 if (!$includeWysiwygEditor) {
1103 $includeWysiwygEditor = TRUE;
1104 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1107 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1113 * @param null $required
1114 * @param null $extra
1116 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1117 $this->addElement('select', $id, $title,
1119 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1122 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1130 * @param $attributes
1131 * @param null $required
1132 * @param null $javascriptMethod
1134 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1136 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1139 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1146 public function getRootTitle() {
1153 public function getCompleteTitle() {
1154 return $this->getRootTitle() . $this->getTitle();
1158 * @return CRM_Core_Smarty
1160 static function &getTemplate() {
1161 return self
::$_template;
1165 * @param $elementName
1167 function addUploadElement($elementName) {
1168 $uploadNames = $this->get('uploadNames');
1169 if (!$uploadNames) {
1170 $uploadNames = array();
1172 if (is_array($elementName)) {
1173 foreach ($elementName as $name) {
1174 if (!in_array($name, $uploadNames)) {
1175 $uploadNames[] = $name;
1180 if (!in_array($elementName, $uploadNames)) {
1181 $uploadNames[] = $elementName;
1184 $this->set('uploadNames', $uploadNames);
1186 $config = CRM_Core_Config
::singleton();
1187 if (!empty($uploadNames)) {
1188 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1195 function buttonType() {
1196 $uploadNames = $this->get('uploadNames');
1197 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1198 $this->assign('buttonType', $buttonType);
1207 function getVar($name) {
1208 return isset($this->$name) ?
$this->$name : NULL;
1215 function setVar($name, $value) {
1216 $this->$name = $value;
1220 * Function to add date
1221 * @param string $name name of the element
1222 * @param string $label label of the element
1223 * @param array $attributes key / value pair
1226 * $attributes = array ( 'addTime' => true,
1227 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1229 * @param boolean $required true if required
1232 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1233 if (!empty($attributes['formatType'])) {
1234 // get actual format
1235 $params = array('name' => $attributes['formatType']);
1238 // cache date information
1240 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1241 if (empty($dateFormat[$key])) {
1242 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1243 $dateFormat[$key] = $values;
1246 $values = $dateFormat[$key];
1249 if ($values['date_format']) {
1250 $attributes['format'] = $values['date_format'];
1253 if (!empty($values['time_format'])) {
1254 $attributes['timeFormat'] = $values['time_format'];
1256 $attributes['startOffset'] = $values['start'];
1257 $attributes['endOffset'] = $values['end'];
1260 $config = CRM_Core_Config
::singleton();
1261 if (empty($attributes['format'])) {
1262 $attributes['format'] = $config->dateInputFormat
;
1265 if (!isset($attributes['startOffset'])) {
1266 $attributes['startOffset'] = 10;
1269 if (!isset($attributes['endOffset'])) {
1270 $attributes['endOffset'] = 10;
1273 $this->add('text', $name, $label, $attributes);
1275 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1277 if (!isset($attributes['timeFormat'])) {
1278 $timeFormat = $config->timeInputFormat
;
1281 $timeFormat = $attributes['timeFormat'];
1284 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1286 $show24Hours = TRUE;
1287 if ($timeFormat == 1) {
1288 $show24Hours = FALSE;
1291 //CRM-6664 -we are having time element name
1292 //in either flat string or an array format.
1293 $elementName = $name . '_time';
1294 if (substr($name, -1) == ']') {
1295 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1298 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1303 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1304 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1305 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1311 * Function that will add date and time
1313 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1314 $addTime = array('addTime' => TRUE);
1315 if (is_array($attributes)) {
1316 $attributes = array_merge($attributes, $addTime);
1319 $attributes = $addTime;
1322 $this->addDate($name, $label, $required, $attributes);
1326 * add a currency and money element to the form
1328 function addMoney($name,
1332 $addCurrency = TRUE,
1333 $currencyName = 'currency',
1334 $defaultCurrency = NULL,
1335 $freezeCurrency = FALSE
1337 $element = $this->add('text', $name, $label, $attributes, $required);
1338 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1341 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1348 * add currency element to the form
1350 function addCurrency($name = 'currency',
1353 $defaultCurrency = NULL,
1354 $freezeCurrency = FALSE
1356 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1357 $options = array('class' => 'crm-select2 eight');
1359 $currencies = array('' => '') +
$currencies;
1360 $options['placeholder'] = ts('- none -');
1362 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1363 if ($freezeCurrency) {
1366 if (!$defaultCurrency) {
1367 $config = CRM_Core_Config
::singleton();
1368 $defaultCurrency = $config->defaultCurrency
;
1370 $this->setDefaults(array($name => $defaultCurrency));
1374 * Create a single or multiple entity ref field
1375 * @param string $name
1376 * @param string $label
1377 * @param array $props mix of html and widget properties, including:
1378 * - select - params to give to select2 widget
1379 * - entity - defaults to contact
1380 * - create - can the user create a new entity on-the-fly?
1381 * Set to TRUE if entity is contact and you want the default profiles,
1382 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1383 * note that permissions are checked automatically
1384 * - api - array of settings for the getlist api wrapper
1385 * note that it accepts a 'params' setting which will be passed to the underlying api
1386 * - placeholder - string
1388 * - class, etc. - other html properties
1389 * @param bool $required
1392 * @return HTML_QuickForm_Element
1394 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1395 require_once "api/api.php";
1396 $config = CRM_Core_Config
::singleton();
1397 // Default properties
1398 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1399 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1400 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1402 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1403 unset($props['create']);
1406 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1408 $defaults = array();
1409 if (!empty($props['multiple'])) {
1410 $defaults['multiple'] = TRUE;
1412 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1414 $this->formatReferenceFieldAttributes($props);
1415 return $this->add('text', $name, $label, $props, $required);
1421 private function formatReferenceFieldAttributes(&$props) {
1422 $props['data-select-params'] = json_encode($props['select']);
1423 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1424 $props['data-api-entity'] = $props['entity'];
1425 if (!empty($props['create'])) {
1426 $props['data-create-links'] = json_encode($props['create']);
1428 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1432 * Convert all date fields within the params to mysql date ready for the
1433 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1434 * and if time is defined it is incorporated
1436 * @param array $params input params from the form
1438 * @todo it would probably be better to work on $this->_params than a passed array
1439 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1442 function convertDateFieldsToMySQL(&$params){
1443 foreach ($this->_dateFields
as $fieldName => $specs){
1444 if(!empty($params[$fieldName])){
1445 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1446 CRM_Utils_Date
::processDate(
1447 $params[$fieldName],
1448 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1452 if(isset($specs['default'])){
1453 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1460 * @param $elementName
1462 function removeFileRequiredRules($elementName) {
1463 $this->_required
= array_diff($this->_required
, array($elementName));
1464 if (isset($this->_rules
[$elementName])) {
1465 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1466 if ($ruleInfo['type'] == 'uploadedfile') {
1467 unset($this->_rules
[$elementName][$index]);
1470 if (empty($this->_rules
[$elementName])) {
1471 unset($this->_rules
[$elementName]);
1477 * Function that can be defined in Form to override or
1478 * perform specific action on cancel action
1482 function cancelAction() {}
1485 * Helper function to verify that required fields have been filled
1486 * Typically called within the scope of a FormRule function
1488 static function validateMandatoryFields($fields, $values, &$errors) {
1489 foreach ($fields as $name => $fld) {
1490 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1491 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1497 * Get contact if for a form object. Prioritise
1498 * - cid in URL if 0 (on behalf on someoneelse)
1499 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1500 * - logged in user id if it matches the one in the cid in the URL
1501 * - contact id validated from a checksum from a checksum
1502 * - cid from the url if the caller has ACL permission to view
1503 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1505 * @return mixed NULL|integer
1507 function getContactID() {
1508 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1509 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1510 $tempID = $this->_params
['select_contact_id'];
1512 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1513 // event form stores as an indexed array, contribution form not so much...
1514 $tempID = $this->_params
[0]['select_contact_id'];
1517 // force to ignore the authenticated user
1518 if ($tempID === '0' ||
$tempID === 0) {
1519 // we set the cid on the form so that this will be retained for the Confirm page
1520 // in the multi-page form & prevent us returning the $userID when this is called
1522 // we don't really need to set it when $tempID is set because the params have that stored
1523 $this->set('cid', 0);
1527 $userID = $this->getLoggedInUserContactID();
1529 if ($tempID == $userID) {
1533 //check if this is a checksum authentication
1534 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1535 if ($userChecksum) {
1536 //check for anonymous user.
1537 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1542 // check if user has permission, CRM-12062
1543 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1551 * Get the contact id of the logged in user
1553 function getLoggedInUserContactID() {
1554 // check if the user is logged in and has a contact ID
1555 $session = CRM_Core_Session
::singleton();
1556 return $session->get('userID');
1560 * add autoselector field -if user has permission to view contacts
1561 * If adding this to a form you also need to add to the tpl e.g
1563 * {if !empty($selectable)}
1564 * <div class="crm-summary-row">
1565 * <div class="crm-label">{$form.select_contact.label}</div>
1566 * <div class="crm-content">
1567 * {$form.select_contact.html}
1572 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1573 * @param array $autoCompleteField
1575 * @internal param array $field metadata of field to use as selector including
1578 * - url (for ajax lookup)
1580 * @todo add data attributes so we can deal with multiple instances on a form
1582 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1583 $autoCompleteField = array_merge(array(
1584 'id_field' => 'select_contact_id',
1585 'placeholder' => ts('Select someone else ...'),
1586 'show_hide' => TRUE,
1587 'api' => array('params' => array('contact_type' => 'Individual'))
1588 ), $autoCompleteField);
1590 if($this->canUseAjaxContactLookups()) {
1591 $this->assign('selectable', $autoCompleteField['id_field']);
1592 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1594 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1596 'form' => array('autocompletes' => $autoCompleteField),
1597 'ids' => array('profile' => $profiles),
1605 function canUseAjaxContactLookups() {
1606 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1607 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1613 * Add the options appropriate to cid = zero - ie. autocomplete
1615 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1616 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1617 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1618 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1620 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1621 $this->assign('nocid', TRUE);
1622 $profiles = array();
1623 if($this->_values
['custom_pre_id']) {
1624 $profiles[] = $this->_values
['custom_pre_id'];
1626 if($this->_values
['custom_post_id']) {
1627 $profiles[] = $this->_values
['custom_post_id'];
1629 if($onlinePaymentProcessorEnabled) {
1630 $profiles[] = 'billing';
1632 if(!empty($this->_values
)) {
1633 $this->addAutoSelector($profiles);
1638 * Set default values on form for given contact (or no contact defaults)
1640 * @param mixed $profile_id (can be id, or profile name)
1641 * @param integer $contactID
1645 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1647 $defaults = civicrm_api3('profile', 'getsingle', array(
1648 'profile_id' => (array) $profile_id,
1649 'contact_id' => $contactID,
1653 catch (Exception
$e) {
1654 // the try catch block gives us silent failure -not 100% sure this is a good idea
1655 // as silent failures are often worse than noisy ones
1661 * Sets form attribute
1664 function preventAjaxSubmit() {
1665 $this->setAttribute('data-no-ajax-submit', 'true');
1669 * Sets form attribute
1672 function allowAjaxSubmit() {
1673 $this->removeAttribute('data-no-ajax-submit');