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();
116 * constants for attributes for various form elements
117 * attempt to standardize on the number of variations that we
118 * use of the below form elements
122 CONST ATTR_SPACING
= ' ';
125 * All checkboxes are defined with a common prefix. This allows us to
126 * have the same javascript to check / clear all the checkboxes etc
127 * If u have multiple groups of checkboxes, you will need to give them different
128 * ids to avoid potential name collision
130 * @var const string / int
132 CONST CB_PREFIX
= 'mark_x_', CB_PREFIY
= 'mark_y_', CB_PREFIZ
= 'mark_z_', CB_PREFIX_LEN
= 7;
135 * Constructor for the basic form page
137 * We should not use QuickForm directly. This class provides a lot
138 * of default convenient functions, rules and buttons
140 * @param object $state State associated with this form
141 * @param enum $action The mode the form is operating in (None/Create/View/Update/Delete)
142 * @param string $method The type of http method used (GET/POST)
143 * @param string $name The name of the form if different from class name
148 function __construct(
150 $action = CRM_Core_Action
::NONE
,
156 $this->_name
= $name;
159 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
162 $this->HTML_QuickForm_Page($this->_name
, $method);
164 $this->_state
=& $state;
166 $this->_state
->setName($this->_name
);
168 $this->_action
= (int) $action;
170 $this->registerRules();
172 // let the constructor initialize this, should happen only once
173 if (!isset(self
::$_template)) {
174 self
::$_template = CRM_Core_Smarty
::singleton();
177 $this->assign('snippet', CRM_Utils_Array
::value('snippet', $_GET));
180 static function generateID() {
184 * register all the standard rules that most forms potentially use
190 function registerRules() {
191 static $rules = array(
192 'title', 'longTitle', 'variable', 'qfVariable',
193 'phone', 'integer', 'query',
195 'domain', 'numberOfDigit',
196 'date', 'currentDate',
197 'asciiFile', 'htmlFile', 'utf8File',
198 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
199 'xssString', 'fileExists', 'autocomplete', 'validContact',
202 foreach ($rules as $rule) {
203 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
208 * Simple easy to use wrapper around addElement. Deal with
209 * simple validation rules
211 * @param string type of html element to be added
212 * @param string name of the html element
213 * @param string display label for the html element
214 * @param string attributes used for this element.
215 * These are not default values
216 * @param bool is this a required field
218 * @return HTML_QuickForm_Element could be an error object
222 function &add($type, $name, $label = '',
223 $attributes = '', $required = FALSE, $extra = NULL
225 // Normalize this property
226 if ($type == 'select' && is_array($extra) && !empty($extra['multiple'])) {
227 $extra['multiple'] = 'multiple';
229 $element = $this->addElement($type, $name, $label, $attributes, $extra);
230 if (HTML_QuickForm
::isError($element)) {
231 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
235 if ($type == 'file') {
236 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
239 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
241 if (HTML_QuickForm
::isError($error)) {
242 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
250 * This function is called before buildForm. Any pre-processing that
251 * needs to be done for buildForm should be done here
253 * This is a virtual function and should be redefined if needed
260 function preProcess() {}
263 * This function is called after the form is validated. Any
264 * processing of form state etc should be done in this function.
265 * Typically all processing associated with a form should be done
266 * here and relevant state should be stored in the session
268 * This is a virtual function and should be redefined if needed
275 function postProcess() {}
278 * This function is just a wrapper, so that we can call all the hook functions
280 function mainProcess() {
281 $this->postProcess();
282 $this->postProcessHook();
284 // Respond with JSON if in AJAX context (also support legacy value '6')
285 if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
286 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
287 $this->ajaxResponse
['action'] = $this->_action
;
288 if (isset($this->_id
) ||
isset($this->id
)) {
289 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
291 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
296 * The postProcess hook is typically called by the framework
297 * However in a few cases, the form exits or redirects early in which
298 * case it needs to call this function so other modules can do the needful
299 * Calling this function directly should be avoided if possible. In general a
300 * better way is to do setUserContext so the framework does the redirect
303 function postProcessHook() {
304 CRM_Utils_Hook
::postProcess(get_class($this), $this);
308 * This virtual function is used to build the form. It replaces the
309 * buildForm associated with QuickForm_Page. This allows us to put
310 * preProcess in front of the actual form building routine
317 function buildQuickForm() {}
320 * This virtual function is used to set the default values of
321 * various form elements
325 * @return array reference to the array of default values
328 function setDefaultValues() {}
331 * This is a virtual function that adds group and global rules to
332 * the form. Keeping it distinct from the form to keep code small
333 * and localized in the form building code
340 function addRules() {}
342 function validate() {
343 $error = parent
::validate();
345 $hookErrors = CRM_Utils_Hook
::validate(
347 $this->_submitValues
,
352 if (!is_array($hookErrors)) {
353 $hookErrors = array();
356 CRM_Utils_Hook
::validateForm(
358 $this->_submitValues
,
364 if (!empty($hookErrors)) {
365 $this->_errors +
= $hookErrors;
368 return (0 == count($this->_errors
));
372 * Core function that builds the form. We redefine this function
373 * here and expect all CRM forms to build their form in the function
377 function buildForm() {
378 $this->_formBuilt
= TRUE;
382 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
385 $this->controller
->_key
&&
386 $this->controller
->_generateQFKey
388 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
389 $this->assign('qfKey', $this->controller
->_key
);
393 // _generateQFKey suppresses the qfKey generation on form snippets that
394 // are part of other forms, hence we use that to avoid adding entryURL
395 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
396 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
399 $this->buildQuickForm();
401 $defaults = $this->setDefaultValues();
402 unset($defaults['qfKey']);
404 if (!empty($defaults)) {
405 $this->setDefaults($defaults);
408 // call the form hook
409 // also call the hook function so any modules can set thier own custom defaults
410 // the user can do both the form and set default values with this hook
411 CRM_Utils_Hook
::buildForm(get_class($this), $this);
417 * Add default Next / Back buttons
419 * @param array array of associative arrays in the order in which the buttons should be
420 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
421 * The base form class will define a bunch of static arrays for commonly used
429 function addButtons($params) {
432 foreach ($params as $button) {
433 $js = CRM_Utils_Array
::value('js', $button);
434 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
436 $attrs = array('class' => 'form-submit default');
439 $attrs = array('class' => 'form-submit');
443 $attrs = array_merge($js, $attrs);
446 if ($button['type'] === 'cancel') {
447 $attrs['class'] .= ' cancel';
450 if ($button['type'] === 'reset') {
451 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
454 if (!empty($button['subName'])) {
455 $buttonName = $this->getButtonName($button['type'], $button['subName']);
458 $buttonName = $this->getButtonName($button['type']);
461 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
462 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
464 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
466 if (!empty($button['isDefault'])) {
467 $this->setDefaultAction($button['type']);
470 // if button type is upload, set the enctype
471 if ($button['type'] == 'upload') {
472 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
473 $this->setMaxFileSize();
476 // hack - addGroup uses an array to express variable spacing, read from the last element
477 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
479 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
483 * getter function for Name
493 * getter function for State
498 function &getState() {
499 return $this->_state
;
503 * getter function for StateType
508 function getStateType() {
509 return $this->_state
->getType();
513 * getter function for title. Should be over-ridden by derived class
518 function getTitle() {
519 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
523 * setter function for title.
525 * @param string $title the title of the form
530 function setTitle($title) {
531 $this->_title
= $title;
535 * Setter function for options
542 function setOptions($options) {
543 $this->_options
= $options;
547 * getter function for link.
553 $config = CRM_Core_Config
::singleton();
554 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
555 '_qf_' . $this->_name
. '_display=true'
560 * boolean function to determine if this is a one form page
565 function isSimpleForm() {
566 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
570 * getter function for Form Action
575 function getFormAction() {
576 return $this->_attributes
['action'];
580 * setter function for Form Action
587 function setFormAction($action) {
588 $this->_attributes
['action'] = $action;
592 * render form and return contents
597 function toSmarty() {
598 $renderer = $this->getRenderer();
599 $this->accept($renderer);
600 $content = $renderer->toArray();
601 $content['formName'] = $this->getName();
606 * getter function for renderer. If renderer is not set
607 * create one and initialize it
612 function &getRenderer() {
613 if (!isset($this->_renderer
)) {
614 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
616 return $this->_renderer
;
620 * Use the form name to create the tpl file name
625 function getTemplateFileName() {
626 $ext = CRM_Extension_System
::singleton()->getMapper();
627 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
628 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
629 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
632 $tplname = str_replace('_',
634 CRM_Utils_System
::getClassName($this)
641 * A wrapper for getTemplateFileName that includes calling the hook to
642 * prevent us from having to copy & paste the logic of calling the hook
644 function getHookedTemplateFileName() {
645 $pageTemplateFile = $this->getTemplateFileName();
646 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
647 return $pageTemplateFile;
651 * Default extra tpl file basically just replaces .tpl with .extra.tpl
652 * i.e. we dont override
657 function overrideExtraTemplateFileName() {
662 * Error reporting mechanism
664 * @param string $message Error Message
665 * @param int $code Error Code
666 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
671 function error($message, $code = NULL, $dao = NULL) {
673 $dao->query('ROLLBACK');
676 $error = CRM_Core_Error
::singleton();
678 $error->push($code, $message);
682 * Store the variable with the value in the form scope
684 * @param string name : name of the variable
685 * @param mixed value : value of the variable
692 function set($name, $value) {
693 $this->controller
->set($name, $value);
697 * Get the variable from the form scope
699 * @param string name : name of the variable
706 function get($name) {
707 return $this->controller
->get($name);
716 function getAction() {
717 return $this->_action
;
723 * @param int $action the mode we want to set the form
728 function setAction($action) {
729 $this->_action
= $action;
733 * assign value to name in template
735 * @param array|string $name name of variable
736 * @param mixed $value value of varaible
741 function assign($var, $value = NULL) {
742 self
::$_template->assign($var, $value);
746 * assign value to name in template by reference
748 * @param array|string $name name of variable
749 * @param mixed $value value of varaible
754 function assign_by_ref($var, &$value) {
755 self
::$_template->assign_by_ref($var, $value);
759 * appends values to template variables
761 * @param array|string $tpl_var the template variable name(s)
762 * @param mixed $value the value to append
765 function append($tpl_var, $value=NULL, $merge=FALSE) {
766 self
::$_template->append($tpl_var, $value, $merge);
770 * Returns an array containing template variables
772 * @param string $name
773 * @param string $type
776 function get_template_vars($name=null) {
777 return self
::$_template->get_template_vars($name);
780 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
782 $attributes = $attributes ?
$attributes : array();
783 $allowClear = !empty($attributes['allowClear']);
784 unset($attributes['allowClear']);
785 $attributes +
= array('id_suffix' => $name);
786 foreach ($values as $key => $var) {
787 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
789 $group = $this->addGroup($options, $name, $title, $separator);
791 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
794 $group->setAttribute('allowClear', TRUE);
799 function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
800 $attributes +
= array('id_suffix' => $id);
802 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
803 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
805 $group = $this->addGroup($choice, $id, $title);
807 $group->setAttribute('allowClear', TRUE);
810 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
814 function addCheckBox($id, $title, $values, $other = NULL,
815 $attributes = NULL, $required = NULL,
816 $javascriptMethod = NULL,
817 $separator = '<br />', $flipValues = FALSE
821 if ($javascriptMethod) {
822 foreach ($values as $key => $var) {
824 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
827 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
832 foreach ($values as $key => $var) {
834 $options[] = $this->createElement('checkbox', $var, NULL, $key);
837 $options[] = $this->createElement('checkbox', $key, NULL, $var);
842 $this->addGroup($options, $id, $title, $separator);
845 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
850 ts('%1 is a required field.', array(1 => $title)),
856 function resetValues() {
857 $data = $this->controller
->container();
858 $data['values'][$this->_name
] = array();
862 * simple shell that derived classes can call to add buttons to
863 * the form with a customized title for the main Submit
865 * @param string $title title of the main button
866 * @param string $type button type for the form after processing
867 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
872 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
874 if ($backType != NULL) {
877 'name' => ts('Previous'),
880 if ($nextType != NULL) {
887 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
889 $buttons[] = $nextButton;
891 $this->addButtons($buttons);
894 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
896 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
897 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
899 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
900 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
905 * Adds a select based on field metadata
906 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
907 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
908 * @param $name - field name to go on the form
909 * @param array $props - mix of html attributes and special properties, namely
910 * - entity (api entity name, can usually be inferred automatically from the form class)
911 * - field (field name - only needed if different from name used on the form)
912 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
913 * - placeholder - set to NULL to disable
915 * @param bool $required
916 * @throws CRM_Core_Exception
917 * @return HTML_QuickForm_Element
919 function addSelect($name, $props = array(), $required = FALSE) {
920 if (!isset($props['entity'])) {
921 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
923 if (!isset($props['field'])) {
924 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
926 $info = civicrm_api3($props['entity'], 'getoptions', array(
927 'field' => $props['field'],
928 'options' => array('metadata' => array('fields'))
931 $options = $info['values'];
932 if (!array_key_exists('placeholder', $props)) {
933 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
935 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
936 $options = array('' => '') +
$options;
938 // Handle custom field
939 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
940 list(, $id) = explode('_', $name);
941 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
942 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
943 $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);
947 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
949 $uniqueName === $props['field'] ||
950 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
951 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
956 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
957 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
959 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
960 $props['data-api-entity'] = $props['entity'];
961 $props['data-api-field'] = $props['field'];
962 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
963 return $this->add('select', $name, $label, $options, $required, $props);
967 * Add a widget for selecting/editing/creating/copying a profile form
969 * @param string $name HTML form-element name
970 * @param string $label Printable label
971 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
972 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
973 * @param array $entities
975 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
977 // FIXME: Instead of adhoc serialization, use a single json_encode()
978 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
979 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
980 $this->add('text', $name, $label, array(
981 'class' => 'crm-profile-selector',
982 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
983 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
984 'data-entities' => json_encode($entities),
988 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
989 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
990 // 2. Based on the option, initialise proper editor
991 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
994 $editor = strtolower(CRM_Utils_Array
::value($editorID,
995 CRM_Core_OptionGroup
::values('wysiwyg_editor')
997 if (!$editor ||
$forceTextarea) {
998 $editor = 'textarea';
1000 if ($editor == 'joomla default editor') {
1001 $editor = 'joomlaeditor';
1004 if ($editor == 'drupal default editor') {
1005 $editor = 'drupalwysiwyg';
1008 //lets add the editor as a attribute
1009 $attributes['editor'] = $editor;
1011 $this->addElement($editor, $name, $label, $attributes);
1012 $this->assign('editor', $editor);
1014 // include wysiwyg editor js files
1015 // FIXME: This code does not make any sense
1016 $includeWysiwygEditor = FALSE;
1017 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1018 if (!$includeWysiwygEditor) {
1019 $includeWysiwygEditor = TRUE;
1020 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1023 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1026 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1027 $this->addElement('select', $id, $title,
1029 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1032 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1036 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1038 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1041 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1045 public function getRootTitle() {
1049 public function getCompleteTitle() {
1050 return $this->getRootTitle() . $this->getTitle();
1053 static function &getTemplate() {
1054 return self
::$_template;
1057 function addUploadElement($elementName) {
1058 $uploadNames = $this->get('uploadNames');
1059 if (!$uploadNames) {
1060 $uploadNames = array();
1062 if (is_array($elementName)) {
1063 foreach ($elementName as $name) {
1064 if (!in_array($name, $uploadNames)) {
1065 $uploadNames[] = $name;
1070 if (!in_array($elementName, $uploadNames)) {
1071 $uploadNames[] = $elementName;
1074 $this->set('uploadNames', $uploadNames);
1076 $config = CRM_Core_Config
::singleton();
1077 if (!empty($uploadNames)) {
1078 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1082 function buttonType() {
1083 $uploadNames = $this->get('uploadNames');
1084 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1085 $this->assign('buttonType', $buttonType);
1089 function getVar($name) {
1090 return isset($this->$name) ?
$this->$name : NULL;
1093 function setVar($name, $value) {
1094 $this->$name = $value;
1098 * Function to add date
1099 * @param string $name name of the element
1100 * @param string $label label of the element
1101 * @param array $attributes key / value pair
1104 * $attributes = array ( 'addTime' => true,
1105 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1107 * @param boolean $required true if required
1110 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1111 if (!empty($attributes['formatType'])) {
1112 // get actual format
1113 $params = array('name' => $attributes['formatType']);
1116 // cache date information
1118 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1119 if (empty($dateFormat[$key])) {
1120 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1121 $dateFormat[$key] = $values;
1124 $values = $dateFormat[$key];
1127 if ($values['date_format']) {
1128 $attributes['format'] = $values['date_format'];
1131 if (!empty($values['time_format'])) {
1132 $attributes['timeFormat'] = $values['time_format'];
1134 $attributes['startOffset'] = $values['start'];
1135 $attributes['endOffset'] = $values['end'];
1138 $config = CRM_Core_Config
::singleton();
1139 if (empty($attributes['format'])) {
1140 $attributes['format'] = $config->dateInputFormat
;
1143 if (!isset($attributes['startOffset'])) {
1144 $attributes['startOffset'] = 10;
1147 if (!isset($attributes['endOffset'])) {
1148 $attributes['endOffset'] = 10;
1151 $this->add('text', $name, $label, $attributes);
1153 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1155 if (!isset($attributes['timeFormat'])) {
1156 $timeFormat = $config->timeInputFormat
;
1159 $timeFormat = $attributes['timeFormat'];
1162 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1164 $show24Hours = TRUE;
1165 if ($timeFormat == 1) {
1166 $show24Hours = FALSE;
1169 //CRM-6664 -we are having time element name
1170 //in either flat string or an array format.
1171 $elementName = $name . '_time';
1172 if (substr($name, -1) == ']') {
1173 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1176 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1181 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1182 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1183 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1189 * Function that will add date and time
1191 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1192 $addTime = array('addTime' => TRUE);
1193 if (is_array($attributes)) {
1194 $attributes = array_merge($attributes, $addTime);
1197 $attributes = $addTime;
1200 $this->addDate($name, $label, $required, $attributes);
1204 * add a currency and money element to the form
1206 function addMoney($name,
1210 $addCurrency = TRUE,
1211 $currencyName = 'currency',
1212 $defaultCurrency = NULL,
1213 $freezeCurrency = FALSE
1215 $element = $this->add('text', $name, $label, $attributes, $required);
1216 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1219 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1226 * add currency element to the form
1228 function addCurrency($name = 'currency',
1231 $defaultCurrency = NULL,
1232 $freezeCurrency = FALSE
1234 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1235 $options = array('class' => 'crm-select2 eight');
1237 $currencies = array('' => '') +
$currencies;
1238 $options['placeholder'] = ts('- none -');
1240 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1241 if ($freezeCurrency) {
1244 if (!$defaultCurrency) {
1245 $config = CRM_Core_Config
::singleton();
1246 $defaultCurrency = $config->defaultCurrency
;
1248 $this->setDefaults(array($name => $defaultCurrency));
1252 * Create a single or multiple entity ref field
1253 * @param string $name
1254 * @param string $label
1255 * @param array $props mix of html and widget properties, including:
1256 * - select - params to give to select2 widget
1257 * - entity - defaults to contact
1258 * - create - can the user create a new entity on-the-fly?
1259 * Set to TRUE if entity is contact and you want the default profiles,
1260 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1261 * note that permissions are checked automatically
1262 * - api - array of settings for the getlist api wrapper
1263 * note that it accepts a 'params' setting which will be passed to the underlying api
1264 * - placeholder - string
1266 * - class, etc. - other html properties
1267 * @param bool $required
1270 * @return HTML_QuickForm_Element
1272 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1273 require_once "api/api.php";
1274 $config = CRM_Core_Config
::singleton();
1275 // Default properties
1276 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1277 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1278 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1280 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1281 unset($props['create']);
1284 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1286 $defaults = array();
1287 if (!empty($props['multiple'])) {
1288 $defaults['multiple'] = TRUE;
1290 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1292 $this->formatReferenceFieldAttributes($props);
1293 return $this->add('text', $name, $label, $props, $required);
1299 private function formatReferenceFieldAttributes(&$props) {
1300 $props['data-select-params'] = json_encode($props['select']);
1301 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1302 $props['data-api-entity'] = $props['entity'];
1303 if (!empty($props['create'])) {
1304 $props['data-create-links'] = json_encode($props['create']);
1306 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1310 * Convert all date fields within the params to mysql date ready for the
1311 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1312 * and if time is defined it is incorporated
1314 * @param array $params input params from the form
1316 * @todo it would probably be better to work on $this->_params than a passed array
1317 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1320 function convertDateFieldsToMySQL(&$params){
1321 foreach ($this->_dateFields
as $fieldName => $specs){
1322 if(!empty($params[$fieldName])){
1323 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1324 CRM_Utils_Date
::processDate(
1325 $params[$fieldName],
1326 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1330 if(isset($specs['default'])){
1331 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1337 function removeFileRequiredRules($elementName) {
1338 $this->_required
= array_diff($this->_required
, array($elementName));
1339 if (isset($this->_rules
[$elementName])) {
1340 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1341 if ($ruleInfo['type'] == 'uploadedfile') {
1342 unset($this->_rules
[$elementName][$index]);
1345 if (empty($this->_rules
[$elementName])) {
1346 unset($this->_rules
[$elementName]);
1352 * Function that can be defined in Form to override or
1353 * perform specific action on cancel action
1357 function cancelAction() {}
1360 * Helper function to verify that required fields have been filled
1361 * Typically called within the scope of a FormRule function
1363 static function validateMandatoryFields($fields, $values, &$errors) {
1364 foreach ($fields as $name => $fld) {
1365 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1366 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1372 * Get contact if for a form object. Prioritise
1373 * - cid in URL if 0 (on behalf on someoneelse)
1374 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1375 * - logged in user id if it matches the one in the cid in the URL
1376 * - contact id validated from a checksum from a checksum
1377 * - cid from the url if the caller has ACL permission to view
1378 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1380 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1382 function getContactID() {
1383 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1384 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1385 $tempID = $this->_params
['select_contact_id'];
1387 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1388 // event form stores as an indexed array, contribution form not so much...
1389 $tempID = $this->_params
[0]['select_contact_id'];
1392 // force to ignore the authenticated user
1393 if ($tempID === '0' ||
$tempID === 0) {
1394 // we set the cid on the form so that this will be retained for the Confirm page
1395 // in the multi-page form & prevent us returning the $userID when this is called
1397 // we don't really need to set it when $tempID is set because the params have that stored
1398 $this->set('cid', 0);
1402 $userID = $this->getLoggedInUserContactID();
1404 if ($tempID == $userID) {
1408 //check if this is a checksum authentication
1409 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1410 if ($userChecksum) {
1411 //check for anonymous user.
1412 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1417 // check if user has permission, CRM-12062
1418 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1426 * Get the contact id of the logged in user
1428 function getLoggedInUserContactID() {
1429 // check if the user is logged in and has a contact ID
1430 $session = CRM_Core_Session
::singleton();
1431 return $session->get('userID');
1435 * add autoselector field -if user has permission to view contacts
1436 * If adding this to a form you also need to add to the tpl e.g
1438 * {if !empty($selectable)}
1439 * <div class="crm-summary-row">
1440 * <div class="crm-label">{$form.select_contact.label}</div>
1441 * <div class="crm-content">
1442 * {$form.select_contact.html}
1446 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1447 * @param array $field metadata of field to use as selector including
1450 * - url (for ajax lookup)
1452 * @todo add data attributes so we can deal with multiple instances on a form
1454 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1455 $autoCompleteField = array_merge(array(
1456 'id_field' => 'select_contact_id',
1457 'placeholder' => ts('Select someone else ...'),
1458 'show_hide' => TRUE,
1459 'api' => array('params' => array('contact_type' => 'Individual'))
1460 ), $autoCompleteField);
1462 if($this->canUseAjaxContactLookups()) {
1463 $this->assign('selectable', $autoCompleteField['id_field']);
1464 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1466 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1468 'form' => array('autocompletes' => $autoCompleteField),
1469 'ids' => array('profile' => $profiles),
1477 function canUseAjaxContactLookups() {
1478 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1479 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1485 * Add the options appropriate to cid = zero - ie. autocomplete
1487 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1488 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1489 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1490 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1492 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1493 $this->assign('nocid', TRUE);
1494 $profiles = array();
1495 if($this->_values
['custom_pre_id']) {
1496 $profiles[] = $this->_values
['custom_pre_id'];
1498 if($this->_values
['custom_post_id']) {
1499 $profiles[] = $this->_values
['custom_post_id'];
1501 if($onlinePaymentProcessorEnabled) {
1502 $profiles[] = 'billing';
1504 if(!empty($this->_values
)) {
1505 $this->addAutoSelector($profiles);
1510 * Set default values on form for given contact (or no contact defaults)
1511 * @param mixed $profile_id (can be id, or profile name)
1512 * @param integer $contactID
1514 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1516 $defaults = civicrm_api3('profile', 'getsingle', array(
1517 'profile_id' => (array) $profile_id,
1518 'contact_id' => $contactID,
1522 catch (Exception
$e) {
1523 // the try catch block gives us silent failure -not 100% sure this is a good idea
1524 // as silent failures are often worse than noisy ones
1530 * Sets form attribute
1533 function preventAjaxSubmit() {
1534 $this->setAttribute('data-no-ajax-submit', 'true');
1538 * Sets form attribute
1541 function allowAjaxSubmit() {
1542 $this->removeAttribute('data-no-ajax-submit');