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';
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 * Url path used to reach this page
113 public $urlPath = array();
115 protected $unsavedWarn;
118 * constants for attributes for various form elements
119 * attempt to standardize on the number of variations that we
120 * use of the below form elements
124 CONST ATTR_SPACING
= ' ';
127 * All checkboxes are defined with a common prefix. This allows us to
128 * have the same javascript to check / clear all the checkboxes etc
129 * If u have multiple groups of checkboxes, you will need to give them different
130 * ids to avoid potential name collision
132 * @var const string / int
134 CONST CB_PREFIX
= 'mark_x_', CB_PREFIY
= 'mark_y_', CB_PREFIZ
= 'mark_z_', CB_PREFIX_LEN
= 7;
137 * Constructor for the basic form page
139 * We should not use QuickForm directly. This class provides a lot
140 * of default convenient functions, rules and buttons
142 * @param object $state State associated with this form
143 * @param enum $action The mode the form is operating in (None/Create/View/Update/Delete)
144 * @param string $method The type of http method used (GET/POST)
145 * @param string $name The name of the form if different from class name
150 function __construct(
152 $action = CRM_Core_Action
::NONE
,
158 $this->_name
= $name;
161 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
164 $this->HTML_QuickForm_Page($this->_name
, $method);
166 $this->_state
=& $state;
168 $this->_state
->setName($this->_name
);
170 $this->_action
= (int) $action;
172 $this->registerRules();
174 // let the constructor initialize this, should happen only once
175 if (!isset(self
::$_template)) {
176 self
::$_template = CRM_Core_Smarty
::singleton();
179 $this->assign('snippet', CRM_Utils_Array
::value('snippet', $_GET));
182 static function generateID() {
186 * register all the standard rules that most forms potentially use
192 function registerRules() {
193 static $rules = array(
194 'title', 'longTitle', 'variable', 'qfVariable',
195 'phone', 'integer', 'query',
197 'domain', 'numberOfDigit',
198 'date', 'currentDate',
199 'asciiFile', 'htmlFile', 'utf8File',
200 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
201 'xssString', 'fileExists', 'autocomplete', 'validContact',
204 foreach ($rules as $rule) {
205 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
210 * Simple easy to use wrapper around addElement. Deal with
211 * simple validation rules
213 * @param string type of html element to be added
214 * @param string name of the html element
215 * @param string display label for the html element
216 * @param string attributes used for this element.
217 * These are not default values
218 * @param bool is this a required field
220 * @return HTML_QuickForm_Element could be an error object
224 function &add($type, $name, $label = '',
225 $attributes = '', $required = FALSE, $extra = NULL
227 // Normalize this property
228 if ($type == 'select' && is_array($extra) && !empty($extra['multiple'])) {
229 $extra['multiple'] = 'multiple';
231 $element = $this->addElement($type, $name, $label, $attributes, $extra);
232 if (HTML_QuickForm
::isError($element)) {
233 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
237 if ($type == 'file') {
238 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
241 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
243 if (HTML_QuickForm
::isError($error)) {
244 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
252 * This function is called before buildForm. Any pre-processing that
253 * needs to be done for buildForm should be done here
255 * This is a virtual function and should be redefined if needed
262 function preProcess() {}
265 * This function is called after the form is validated. Any
266 * processing of form state etc should be done in this function.
267 * Typically all processing associated with a form should be done
268 * here and relevant state should be stored in the session
270 * This is a virtual function and should be redefined if needed
277 function postProcess() {}
280 * This function is just a wrapper, so that we can call all the hook functions
282 function mainProcess() {
283 $this->postProcess();
284 $this->postProcessHook();
286 // Respond with JSON if in AJAX context (also support legacy value '6')
287 if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
288 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
289 $this->ajaxResponse
['action'] = $this->_action
;
290 if (isset($this->_id
) ||
isset($this->id
)) {
291 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
293 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
298 * The postProcess hook is typically called by the framework
299 * However in a few cases, the form exits or redirects early in which
300 * case it needs to call this function so other modules can do the needful
301 * Calling this function directly should be avoided if possible. In general a
302 * better way is to do setUserContext so the framework does the redirect
305 function postProcessHook() {
306 CRM_Utils_Hook
::postProcess(get_class($this), $this);
310 * This virtual function is used to build the form. It replaces the
311 * buildForm associated with QuickForm_Page. This allows us to put
312 * preProcess in front of the actual form building routine
319 function buildQuickForm() {}
322 * This virtual function is used to set the default values of
323 * various form elements
327 * @return array reference to the array of default values
330 function setDefaultValues() {}
333 * This is a virtual function that adds group and global rules to
334 * the form. Keeping it distinct from the form to keep code small
335 * and localized in the form building code
342 function addRules() {}
344 function validate() {
345 $error = parent
::validate();
347 $hookErrors = CRM_Utils_Hook
::validate(
349 $this->_submitValues
,
354 if (!is_array($hookErrors)) {
355 $hookErrors = array();
358 CRM_Utils_Hook
::validateForm(
360 $this->_submitValues
,
366 if (!empty($hookErrors)) {
367 $this->_errors +
= $hookErrors;
370 return (0 == count($this->_errors
));
374 * Core function that builds the form. We redefine this function
375 * here and expect all CRM forms to build their form in the function
379 function buildForm() {
380 $this->_formBuilt
= TRUE;
384 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
387 $this->controller
->_key
&&
388 $this->controller
->_generateQFKey
390 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
391 $this->assign('qfKey', $this->controller
->_key
);
395 // _generateQFKey suppresses the qfKey generation on form snippets that
396 // are part of other forms, hence we use that to avoid adding entryURL
397 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
398 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
401 $this->buildQuickForm();
403 $defaults = $this->setDefaultValues();
404 unset($defaults['qfKey']);
406 if (!empty($defaults)) {
407 $this->setDefaults($defaults);
410 // call the form hook
411 // also call the hook function so any modules can set thier own custom defaults
412 // the user can do both the form and set default values with this hook
413 CRM_Utils_Hook
::buildForm(get_class($this), $this);
419 * Add default Next / Back buttons
421 * @param array array of associative arrays in the order in which the buttons should be
422 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
423 * The base form class will define a bunch of static arrays for commonly used
431 function addButtons($params) {
434 foreach ($params as $button) {
435 $js = CRM_Utils_Array
::value('js', $button);
436 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
438 $attrs = array('class' => 'form-submit default');
441 $attrs = array('class' => 'form-submit');
445 $attrs = array_merge($js, $attrs);
448 if ($button['type'] === 'cancel') {
449 $attrs['class'] .= ' cancel';
452 if ($button['type'] === 'reset') {
453 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
456 if (!empty($button['subName'])) {
457 $buttonName = $this->getButtonName($button['type'], $button['subName']);
460 $buttonName = $this->getButtonName($button['type']);
463 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
464 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
466 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
468 if (!empty($button['isDefault'])) {
469 $this->setDefaultAction($button['type']);
472 // if button type is upload, set the enctype
473 if ($button['type'] == 'upload') {
474 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
475 $this->setMaxFileSize();
478 // hack - addGroup uses an array to express variable spacing, read from the last element
479 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
481 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
485 * getter function for Name
495 * getter function for State
500 function &getState() {
501 return $this->_state
;
505 * getter function for StateType
510 function getStateType() {
511 return $this->_state
->getType();
515 * getter function for title. Should be over-ridden by derived class
520 function getTitle() {
521 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
525 * setter function for title.
527 * @param string $title the title of the form
532 function setTitle($title) {
533 $this->_title
= $title;
537 * Setter function for options
544 function setOptions($options) {
545 $this->_options
= $options;
549 * getter function for link.
555 $config = CRM_Core_Config
::singleton();
556 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
557 '_qf_' . $this->_name
. '_display=true'
562 * boolean function to determine if this is a one form page
567 function isSimpleForm() {
568 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
572 * getter function for Form Action
577 function getFormAction() {
578 return $this->_attributes
['action'];
582 * setter function for Form Action
589 function setFormAction($action) {
590 $this->_attributes
['action'] = $action;
594 * render form and return contents
599 function toSmarty() {
600 $renderer = $this->getRenderer();
601 $this->accept($renderer);
602 $content = $renderer->toArray();
603 $content['formName'] = $this->getName();
608 * getter function for renderer. If renderer is not set
609 * create one and initialize it
614 function &getRenderer() {
615 if (!isset($this->_renderer
)) {
616 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
618 return $this->_renderer
;
622 * Use the form name to create the tpl file name
627 function getTemplateFileName() {
628 $ext = CRM_Extension_System
::singleton()->getMapper();
629 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
630 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
631 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
634 $tplname = str_replace('_',
636 CRM_Utils_System
::getClassName($this)
643 * A wrapper for getTemplateFileName that includes calling the hook to
644 * prevent us from having to copy & paste the logic of calling the hook
646 function getHookedTemplateFileName() {
647 $pageTemplateFile = $this->getTemplateFileName();
648 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
649 return $pageTemplateFile;
653 * Default extra tpl file basically just replaces .tpl with .extra.tpl
654 * i.e. we dont override
659 function overrideExtraTemplateFileName() {
664 * Error reporting mechanism
666 * @param string $message Error Message
667 * @param int $code Error Code
668 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
673 function error($message, $code = NULL, $dao = NULL) {
675 $dao->query('ROLLBACK');
678 $error = CRM_Core_Error
::singleton();
680 $error->push($code, $message);
684 * Store the variable with the value in the form scope
686 * @param string name : name of the variable
687 * @param mixed value : value of the variable
694 function set($name, $value) {
695 $this->controller
->set($name, $value);
699 * Get the variable from the form scope
701 * @param string name : name of the variable
708 function get($name) {
709 return $this->controller
->get($name);
718 function getAction() {
719 return $this->_action
;
725 * @param int $action the mode we want to set the form
730 function setAction($action) {
731 $this->_action
= $action;
735 * assign value to name in template
737 * @param array|string $name name of variable
738 * @param mixed $value value of varaible
743 function assign($var, $value = NULL) {
744 self
::$_template->assign($var, $value);
748 * assign value to name in template by reference
750 * @param array|string $name name of variable
751 * @param mixed $value value of varaible
756 function assign_by_ref($var, &$value) {
757 self
::$_template->assign_by_ref($var, $value);
761 * appends values to template variables
763 * @param array|string $tpl_var the template variable name(s)
764 * @param mixed $value the value to append
767 function append($tpl_var, $value=NULL, $merge=FALSE) {
768 self
::$_template->append($tpl_var, $value, $merge);
772 * Returns an array containing template variables
774 * @param string $name
775 * @param string $type
778 function get_template_vars($name=null) {
779 return self
::$_template->get_template_vars($name);
782 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
784 $attributes = $attributes ?
$attributes : array();
785 $allowClear = !empty($attributes['allowClear']);
786 unset($attributes['allowClear']);
787 $attributes +
= array('id_suffix' => $name);
788 foreach ($values as $key => $var) {
789 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
791 $group = $this->addGroup($options, $name, $title, $separator);
793 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
796 $group->setAttribute('allowClear', TRUE);
801 function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
802 $attributes +
= array('id_suffix' => $id);
804 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
805 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
807 $group = $this->addGroup($choice, $id, $title);
809 $group->setAttribute('allowClear', TRUE);
812 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
816 function addCheckBox($id, $title, $values, $other = NULL,
817 $attributes = NULL, $required = NULL,
818 $javascriptMethod = NULL,
819 $separator = '<br />', $flipValues = FALSE
823 if ($javascriptMethod) {
824 foreach ($values as $key => $var) {
826 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
829 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
834 foreach ($values as $key => $var) {
836 $options[] = $this->createElement('checkbox', $var, NULL, $key);
839 $options[] = $this->createElement('checkbox', $key, NULL, $var);
844 $this->addGroup($options, $id, $title, $separator);
847 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
852 ts('%1 is a required field.', array(1 => $title)),
858 function resetValues() {
859 $data = $this->controller
->container();
860 $data['values'][$this->_name
] = array();
864 * simple shell that derived classes can call to add buttons to
865 * the form with a customized title for the main Submit
867 * @param string $title title of the main button
868 * @param string $type button type for the form after processing
869 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
874 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
876 if ($backType != NULL) {
879 'name' => ts('Previous'),
882 if ($nextType != NULL) {
889 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
891 $buttons[] = $nextButton;
893 $this->addButtons($buttons);
896 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
898 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
899 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
901 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
902 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
907 * Adds a select based on field metadata
908 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
909 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
910 * @param $name - field name to go on the form
911 * @param array $props - mix of html attributes and special properties, namely
912 * - entity (api entity name, can usually be inferred automatically from the form class)
913 * - field (field name - only needed if different from name used on the form)
914 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
915 * - placeholder - set to NULL to disable
917 * @param bool $required
918 * @throws CRM_Core_Exception
919 * @return HTML_QuickForm_Element
921 function addSelect($name, $props = array(), $required = FALSE) {
922 if (!isset($props['entity'])) {
923 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
925 if (!isset($props['field'])) {
926 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
928 $info = civicrm_api3($props['entity'], 'getoptions', array(
929 'field' => $props['field'],
930 'options' => array('metadata' => array('fields'))
933 $options = $info['values'];
934 if (!array_key_exists('placeholder', $props)) {
935 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
937 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
938 $options = array('' => '') +
$options;
940 // Handle custom field
941 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
942 list(, $id) = explode('_', $name);
943 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
944 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
945 $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);
949 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
951 $uniqueName === $props['field'] ||
952 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
953 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
958 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
959 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
961 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
962 $props['data-api-entity'] = $props['entity'];
963 $props['data-api-field'] = $props['field'];
964 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
965 return $this->add('select', $name, $label, $options, $required, $props);
969 * Add a widget for selecting/editing/creating/copying a profile form
971 * @param string $name HTML form-element name
972 * @param string $label Printable label
973 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
974 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
975 * @param array $entities
977 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
979 // FIXME: Instead of adhoc serialization, use a single json_encode()
980 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
981 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
982 $this->add('text', $name, $label, array(
983 'class' => 'crm-profile-selector',
984 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
985 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
986 'data-entities' => json_encode($entities),
990 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
991 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
992 // 2. Based on the option, initialise proper editor
993 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
996 $editor = strtolower(CRM_Utils_Array
::value($editorID,
997 CRM_Core_OptionGroup
::values('wysiwyg_editor')
999 if (!$editor ||
$forceTextarea) {
1000 $editor = 'textarea';
1002 if ($editor == 'joomla default editor') {
1003 $editor = 'joomlaeditor';
1006 if ($editor == 'drupal default editor') {
1007 $editor = 'drupalwysiwyg';
1010 //lets add the editor as a attribute
1011 $attributes['editor'] = $editor;
1013 $this->addElement($editor, $name, $label, $attributes);
1014 $this->assign('editor', $editor);
1016 // include wysiwyg editor js files
1017 // FIXME: This code does not make any sense
1018 $includeWysiwygEditor = FALSE;
1019 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1020 if (!$includeWysiwygEditor) {
1021 $includeWysiwygEditor = TRUE;
1022 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1025 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1028 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1029 $this->addElement('select', $id, $title,
1031 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1034 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1038 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1040 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1043 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1047 public function getRootTitle() {
1051 public function getCompleteTitle() {
1052 return $this->getRootTitle() . $this->getTitle();
1055 static function &getTemplate() {
1056 return self
::$_template;
1059 function addUploadElement($elementName) {
1060 $uploadNames = $this->get('uploadNames');
1061 if (!$uploadNames) {
1062 $uploadNames = array();
1064 if (is_array($elementName)) {
1065 foreach ($elementName as $name) {
1066 if (!in_array($name, $uploadNames)) {
1067 $uploadNames[] = $name;
1072 if (!in_array($elementName, $uploadNames)) {
1073 $uploadNames[] = $elementName;
1076 $this->set('uploadNames', $uploadNames);
1078 $config = CRM_Core_Config
::singleton();
1079 if (!empty($uploadNames)) {
1080 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1084 function buttonType() {
1085 $uploadNames = $this->get('uploadNames');
1086 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1087 $this->assign('buttonType', $buttonType);
1091 function getVar($name) {
1092 return isset($this->$name) ?
$this->$name : NULL;
1095 function setVar($name, $value) {
1096 $this->$name = $value;
1100 * Function to add date
1101 * @param string $name name of the element
1102 * @param string $label label of the element
1103 * @param array $attributes key / value pair
1106 * $attributes = array ( 'addTime' => true,
1107 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1109 * @param boolean $required true if required
1112 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1113 if (!empty($attributes['formatType'])) {
1114 // get actual format
1115 $params = array('name' => $attributes['formatType']);
1118 // cache date information
1120 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1121 if (empty($dateFormat[$key])) {
1122 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1123 $dateFormat[$key] = $values;
1126 $values = $dateFormat[$key];
1129 if ($values['date_format']) {
1130 $attributes['format'] = $values['date_format'];
1133 if (!empty($values['time_format'])) {
1134 $attributes['timeFormat'] = $values['time_format'];
1136 $attributes['startOffset'] = $values['start'];
1137 $attributes['endOffset'] = $values['end'];
1140 $config = CRM_Core_Config
::singleton();
1141 if (empty($attributes['format'])) {
1142 $attributes['format'] = $config->dateInputFormat
;
1145 if (!isset($attributes['startOffset'])) {
1146 $attributes['startOffset'] = 10;
1149 if (!isset($attributes['endOffset'])) {
1150 $attributes['endOffset'] = 10;
1153 $this->add('text', $name, $label, $attributes);
1155 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1157 if (!isset($attributes['timeFormat'])) {
1158 $timeFormat = $config->timeInputFormat
;
1161 $timeFormat = $attributes['timeFormat'];
1164 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1166 $show24Hours = TRUE;
1167 if ($timeFormat == 1) {
1168 $show24Hours = FALSE;
1171 //CRM-6664 -we are having time element name
1172 //in either flat string or an array format.
1173 $elementName = $name . '_time';
1174 if (substr($name, -1) == ']') {
1175 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1178 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1183 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1184 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1185 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1191 * Function that will add date and time
1193 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1194 $addTime = array('addTime' => TRUE);
1195 if (is_array($attributes)) {
1196 $attributes = array_merge($attributes, $addTime);
1199 $attributes = $addTime;
1202 $this->addDate($name, $label, $required, $attributes);
1206 * add a currency and money element to the form
1208 function addMoney($name,
1212 $addCurrency = TRUE,
1213 $currencyName = 'currency',
1214 $defaultCurrency = NULL,
1215 $freezeCurrency = FALSE
1217 $element = $this->add('text', $name, $label, $attributes, $required);
1218 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1221 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1228 * add currency element to the form
1230 function addCurrency($name = 'currency',
1233 $defaultCurrency = NULL,
1234 $freezeCurrency = FALSE
1236 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1237 $options = array('class' => 'crm-select2 eight');
1239 $currencies = array('' => '') +
$currencies;
1240 $options['placeholder'] = ts('- none -');
1242 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1243 if ($freezeCurrency) {
1246 if (!$defaultCurrency) {
1247 $config = CRM_Core_Config
::singleton();
1248 $defaultCurrency = $config->defaultCurrency
;
1250 $this->setDefaults(array($name => $defaultCurrency));
1254 * Create a single or multiple entity ref field
1255 * @param string $name
1256 * @param string $label
1257 * @param array $props mix of html and widget properties, including:
1258 * - select - params to give to select2 widget
1259 * - entity - defaults to contact
1260 * - create - can the user create a new entity on-the-fly?
1261 * Set to TRUE if entity is contact and you want the default profiles,
1262 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1263 * note that permissions are checked automatically
1264 * - api - array of settings for the getlist api wrapper
1265 * note that it accepts a 'params' setting which will be passed to the underlying api
1266 * - placeholder - string
1268 * - class, etc. - other html properties
1269 * @param bool $required
1272 * @return HTML_QuickForm_Element
1274 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1275 require_once "api/api.php";
1276 $config = CRM_Core_Config
::singleton();
1277 // Default properties
1278 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1279 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1280 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1282 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1283 unset($props['create']);
1286 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1288 $defaults = array();
1289 if (!empty($props['multiple'])) {
1290 $defaults['multiple'] = TRUE;
1292 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1294 $this->formatReferenceFieldAttributes($props);
1295 return $this->add('text', $name, $label, $props, $required);
1301 private function formatReferenceFieldAttributes(&$props) {
1302 $props['data-select-params'] = json_encode($props['select']);
1303 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1304 $props['data-api-entity'] = $props['entity'];
1305 if (!empty($props['create'])) {
1306 $props['data-create-links'] = json_encode($props['create']);
1308 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1312 * Convert all date fields within the params to mysql date ready for the
1313 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1314 * and if time is defined it is incorporated
1316 * @param array $params input params from the form
1318 * @todo it would probably be better to work on $this->_params than a passed array
1319 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1322 function convertDateFieldsToMySQL(&$params){
1323 foreach ($this->_dateFields
as $fieldName => $specs){
1324 if(!empty($params[$fieldName])){
1325 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1326 CRM_Utils_Date
::processDate(
1327 $params[$fieldName],
1328 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1332 if(isset($specs['default'])){
1333 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1339 function removeFileRequiredRules($elementName) {
1340 $this->_required
= array_diff($this->_required
, array($elementName));
1341 if (isset($this->_rules
[$elementName])) {
1342 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1343 if ($ruleInfo['type'] == 'uploadedfile') {
1344 unset($this->_rules
[$elementName][$index]);
1347 if (empty($this->_rules
[$elementName])) {
1348 unset($this->_rules
[$elementName]);
1354 * Function that can be defined in Form to override or
1355 * perform specific action on cancel action
1359 function cancelAction() {}
1362 * Helper function to verify that required fields have been filled
1363 * Typically called within the scope of a FormRule function
1365 static function validateMandatoryFields($fields, $values, &$errors) {
1366 foreach ($fields as $name => $fld) {
1367 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1368 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1374 * Get contact if for a form object. Prioritise
1375 * - cid in URL if 0 (on behalf on someoneelse)
1376 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1377 * - logged in user id if it matches the one in the cid in the URL
1378 * - contact id validated from a checksum from a checksum
1379 * - cid from the url if the caller has ACL permission to view
1380 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1382 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1384 function getContactID() {
1385 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1386 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1387 $tempID = $this->_params
['select_contact_id'];
1389 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1390 // event form stores as an indexed array, contribution form not so much...
1391 $tempID = $this->_params
[0]['select_contact_id'];
1394 // force to ignore the authenticated user
1395 if ($tempID === '0' ||
$tempID === 0) {
1396 // we set the cid on the form so that this will be retained for the Confirm page
1397 // in the multi-page form & prevent us returning the $userID when this is called
1399 // we don't really need to set it when $tempID is set because the params have that stored
1400 $this->set('cid', 0);
1404 $userID = $this->getLoggedInUserContactID();
1406 if ($tempID == $userID) {
1410 //check if this is a checksum authentication
1411 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1412 if ($userChecksum) {
1413 //check for anonymous user.
1414 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1419 // check if user has permission, CRM-12062
1420 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1428 * Get the contact id of the logged in user
1430 function getLoggedInUserContactID() {
1431 // check if the user is logged in and has a contact ID
1432 $session = CRM_Core_Session
::singleton();
1433 return $session->get('userID');
1437 * add autoselector field -if user has permission to view contacts
1438 * If adding this to a form you also need to add to the tpl e.g
1440 * {if !empty($selectable)}
1441 * <div class="crm-summary-row">
1442 * <div class="crm-label">{$form.select_contact.label}</div>
1443 * <div class="crm-content">
1444 * {$form.select_contact.html}
1448 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1449 * @param array $field metadata of field to use as selector including
1452 * - url (for ajax lookup)
1454 * @todo add data attributes so we can deal with multiple instances on a form
1456 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1457 $autoCompleteField = array_merge(array(
1458 'id_field' => 'select_contact_id',
1459 'placeholder' => ts('Select someone else ...'),
1460 'show_hide' => TRUE,
1461 'api' => array('params' => array('contact_type' => 'Individual'))
1462 ), $autoCompleteField);
1464 if($this->canUseAjaxContactLookups()) {
1465 $this->assign('selectable', $autoCompleteField['id_field']);
1466 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1468 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1470 'form' => array('autocompletes' => $autoCompleteField),
1471 'ids' => array('profile' => $profiles),
1479 function canUseAjaxContactLookups() {
1480 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1481 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1487 * Add the options appropriate to cid = zero - ie. autocomplete
1489 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1490 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1491 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1492 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1494 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1495 $this->assign('nocid', TRUE);
1496 $profiles = array();
1497 if($this->_values
['custom_pre_id']) {
1498 $profiles[] = $this->_values
['custom_pre_id'];
1500 if($this->_values
['custom_post_id']) {
1501 $profiles[] = $this->_values
['custom_post_id'];
1503 if($onlinePaymentProcessorEnabled) {
1504 $profiles[] = 'billing';
1506 if(!empty($this->_values
)) {
1507 $this->addAutoSelector($profiles);
1512 * Set default values on form for given contact (or no contact defaults)
1513 * @param mixed $profile_id (can be id, or profile name)
1514 * @param integer $contactID
1516 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1518 $defaults = civicrm_api3('profile', 'getsingle', array(
1519 'profile_id' => (array) $profile_id,
1520 'contact_id' => $contactID,
1524 catch (Exception
$e) {
1525 // the try catch block gives us silent failure -not 100% sure this is a good idea
1526 // as silent failures are often worse than noisy ones
1532 * Sets form attribute
1535 function preventAjaxSubmit() {
1536 $this->setAttribute('data-no-ajax-submit', 'true');
1540 * Sets form attribute
1543 function allowAjaxSubmit() {
1544 $this->removeAttribute('data-no-ajax-submit');