3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.4 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * This is our base form. It is part of the Form/Controller/StateMachine
30 * trifecta. Each form is associated with a specific state in the state
31 * machine. Each form can also operate in various modes
34 * @copyright CiviCRM LLC (c) 2004-2013
39 require_once 'HTML/QuickForm/Page.php';
40 class CRM_Core_Form
extends HTML_QuickForm_Page
{
43 * The state object that this form belongs to
49 * The name of this form
55 * The title of this form
58 protected $_title = NULL;
61 * The options passed into this form
64 protected $_options = NULL;
67 * The mode of operation for this form
73 * the renderer used for this form
80 * An array to hold a list of datefields on the form
81 * so that they can be converted to ISO in a consistent manner
85 * e.g on a form declare $_dateFields = array(
86 * 'receive_date' => array('default' => 'now'),
88 * then in postProcess call $this->convertDateFieldsToMySQL($formValues)
89 * to have the time field re-incorporated into the field & 'now' set if
90 * no value has been passed in
92 protected $_dateFields = array();
95 * cache the smarty template for efficiency reasons
97 * @var CRM_Core_Smarty
99 static protected $_template;
102 * What to return to the client if in ajax mode (snippet=json)
106 public $ajaxResponse = array();
109 * 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', (int) CRM_Utils_Array
::value('snippet', $_REQUEST));
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, $javascript = NULL
225 $element = $this->addElement($type, $name, $label, $attributes, $javascript);
226 if (HTML_QuickForm
::isError($element)) {
227 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
231 if ($type == 'file') {
232 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
235 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
237 if (HTML_QuickForm
::isError($error)) {
238 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
246 * This function is called before buildForm. Any pre-processing that
247 * needs to be done for buildForm should be done here
249 * This is a virtual function and should be redefined if needed
256 function preProcess() {}
259 * This function is called after the form is validated. Any
260 * processing of form state etc should be done in this function.
261 * Typically all processing associated with a form should be done
262 * here and relevant state should be stored in the session
264 * This is a virtual function and should be redefined if needed
271 function postProcess() {}
274 * This function is just a wrapper, so that we can call all the hook functions
276 function mainProcess() {
277 $this->postProcess();
278 $this->postProcessHook();
280 // Respond with JSON if in AJAX context (also support legacy value '6')
281 if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
282 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
283 $this->ajaxResponse
['action'] = $this->_action
;
284 if (isset($this->_id
) ||
isset($this->id
)) {
285 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
287 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
292 * The postProcess hook is typically called by the framework
293 * However in a few cases, the form exits or redirects early in which
294 * case it needs to call this function so other modules can do the needful
295 * Calling this function directly should be avoided if possible. In general a
296 * better way is to do setUserContext so the framework does the redirect
299 function postProcessHook() {
300 CRM_Utils_Hook
::postProcess(get_class($this), $this);
304 * This virtual function is used to build the form. It replaces the
305 * buildForm associated with QuickForm_Page. This allows us to put
306 * preProcess in front of the actual form building routine
313 function buildQuickForm() {}
316 * This virtual function is used to set the default values of
317 * various form elements
321 * @return array reference to the array of default values
324 function setDefaultValues() {}
327 * This is a virtual function that adds group and global rules to
328 * the form. Keeping it distinct from the form to keep code small
329 * and localized in the form building code
336 function addRules() {}
338 function validate() {
339 $error = parent
::validate();
341 $hookErrors = CRM_Utils_Hook
::validate(
343 $this->_submitValues
,
348 if (!is_array($hookErrors)) {
349 $hookErrors = array();
352 CRM_Utils_Hook
::validateForm(
354 $this->_submitValues
,
360 if (!empty($hookErrors)) {
361 $this->_errors +
= $hookErrors;
364 return (0 == count($this->_errors
));
368 * Core function that builds the form. We redefine this function
369 * here and expect all CRM forms to build their form in the function
373 function buildForm() {
374 $this->_formBuilt
= TRUE;
378 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
381 $this->controller
->_key
&&
382 $this->controller
->_generateQFKey
384 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
385 $this->assign('qfKey', $this->controller
->_key
);
389 // _generateQFKey suppresses the qfKey generation on form snippets that
390 // are part of other forms, hence we use that to avoid adding entryURL
391 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
392 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
395 $this->buildQuickForm();
397 $defaults = $this->setDefaultValues();
398 unset($defaults['qfKey']);
400 if (!empty($defaults)) {
401 $this->setDefaults($defaults);
404 // call the form hook
405 // also call the hook function so any modules can set thier own custom defaults
406 // the user can do both the form and set default values with this hook
407 CRM_Utils_Hook
::buildForm(get_class($this), $this);
413 * Add default Next / Back buttons
415 * @param array array of associative arrays in the order in which the buttons should be
416 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
417 * The base form class will define a bunch of static arrays for commonly used
425 function addButtons($params) {
428 foreach ($params as $button) {
429 $js = CRM_Utils_Array
::value('js', $button);
430 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
432 $attrs = array('class' => 'form-submit default');
435 $attrs = array('class' => 'form-submit');
439 $attrs = array_merge($js, $attrs);
442 if ($button['type'] === 'reset') {
443 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
446 if (!empty($button['subName'])) {
447 $buttonName = $this->getButtonName($button['type'], $button['subName']);
450 $buttonName = $this->getButtonName($button['type']);
453 if (in_array($button['type'], array(
454 'next', 'upload')) && $button['name'] === 'Save') {
455 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
457 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
459 if (!empty($button['isDefault'])) {
460 $this->setDefaultAction($button['type']);
463 // if button type is upload, set the enctype
464 if ($button['type'] == 'upload') {
465 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
466 $this->setMaxFileSize();
469 // hack - addGroup uses an array to express variable spacing, read from the last element
470 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
472 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
476 * getter function for Name
486 * getter function for State
491 function &getState() {
492 return $this->_state
;
496 * getter function for StateType
501 function getStateType() {
502 return $this->_state
->getType();
506 * getter function for title. Should be over-ridden by derived class
511 function getTitle() {
512 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
516 * setter function for title.
518 * @param string $title the title of the form
523 function setTitle($title) {
524 $this->_title
= $title;
528 * Setter function for options
535 function setOptions($options) {
536 $this->_options
= $options;
540 * getter function for link.
546 $config = CRM_Core_Config
::singleton();
547 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
548 '_qf_' . $this->_name
. '_display=true'
553 * boolean function to determine if this is a one form page
558 function isSimpleForm() {
559 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
563 * getter function for Form Action
568 function getFormAction() {
569 return $this->_attributes
['action'];
573 * setter function for Form Action
580 function setFormAction($action) {
581 $this->_attributes
['action'] = $action;
585 * render form and return contents
590 function toSmarty() {
591 $renderer = $this->getRenderer();
592 $this->accept($renderer);
593 $content = $renderer->toArray();
594 $content['formName'] = $this->getName();
599 * getter function for renderer. If renderer is not set
600 * create one and initialize it
605 function &getRenderer() {
606 if (!isset($this->_renderer
)) {
607 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
609 return $this->_renderer
;
613 * Use the form name to create the tpl file name
618 function getTemplateFileName() {
619 $ext = CRM_Extension_System
::singleton()->getMapper();
620 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
621 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
622 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
625 $tplname = str_replace('_',
627 CRM_Utils_System
::getClassName($this)
634 * A wrapper for getTemplateFileName that includes calling the hook to
635 * prevent us from having to copy & paste the logic of calling the hook
637 function getHookedTemplateFileName() {
638 $pageTemplateFile = $this->getTemplateFileName();
639 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
640 return $pageTemplateFile;
644 * Default extra tpl file basically just replaces .tpl with .extra.tpl
645 * i.e. we dont override
650 function overrideExtraTemplateFileName() {
655 * Error reporting mechanism
657 * @param string $message Error Message
658 * @param int $code Error Code
659 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
664 function error($message, $code = NULL, $dao = NULL) {
666 $dao->query('ROLLBACK');
669 $error = CRM_Core_Error
::singleton();
671 $error->push($code, $message);
675 * Store the variable with the value in the form scope
677 * @param string name : name of the variable
678 * @param mixed value : value of the variable
685 function set($name, $value) {
686 $this->controller
->set($name, $value);
690 * Get the variable from the form scope
692 * @param string name : name of the variable
699 function get($name) {
700 return $this->controller
->get($name);
709 function getAction() {
710 return $this->_action
;
716 * @param int $action the mode we want to set the form
721 function setAction($action) {
722 $this->_action
= $action;
726 * assign value to name in template
728 * @param array|string $name name of variable
729 * @param mixed $value value of varaible
734 function assign($var, $value = NULL) {
735 self
::$_template->assign($var, $value);
739 * assign value to name in template by reference
741 * @param array|string $name name of variable
742 * @param mixed $value value of varaible
747 function assign_by_ref($var, &$value) {
748 self
::$_template->assign_by_ref($var, $value);
752 * appends values to template variables
754 * @param array|string $tpl_var the template variable name(s)
755 * @param mixed $value the value to append
758 function append($tpl_var, $value=NULL, $merge=FALSE) {
759 self
::$_template->append($tpl_var, $value, $merge);
763 * Returns an array containing template variables
765 * @param string $name
766 * @param string $type
769 function get_template_vars($name=null) {
770 return self
::$_template->get_template_vars($name);
773 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
775 $attributes = $attributes ?
$attributes : array();
776 $unselectable = !empty($attributes['unselectable']);
777 unset($attributes['unselectable']);
778 $attributes +
= array('id_suffix' => $name);
779 foreach ($values as $key => $var) {
780 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
782 $group = $this->addGroup($options, $name, $title, $separator);
784 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
787 $group->setAttribute('unselectable', TRUE);
792 function addYesNo($id, $title, $unselectable = FALSE, $required = NULL, $attributes = array()) {
793 $attributes +
= array('id_suffix' => $id);
795 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
796 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
798 $group = $this->addGroup($choice, $id, $title);
800 $group->setAttribute('unselectable', TRUE);
803 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
807 function addCheckBox($id, $title, $values, $other = NULL,
808 $attributes = NULL, $required = NULL,
809 $javascriptMethod = NULL,
810 $separator = '<br />', $flipValues = FALSE
814 if ($javascriptMethod) {
815 foreach ($values as $key => $var) {
817 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
820 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
825 foreach ($values as $key => $var) {
827 $options[] = $this->createElement('checkbox', $var, NULL, $key);
830 $options[] = $this->createElement('checkbox', $key, NULL, $var);
835 $this->addGroup($options, $id, $title, $separator);
838 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
843 ts('%1 is a required field.', array(1 => $title)),
849 function resetValues() {
850 $data = $this->controller
->container();
851 $data['values'][$this->_name
] = array();
855 * simple shell that derived classes can call to add buttons to
856 * the form with a customized title for the main Submit
858 * @param string $title title of the main button
859 * @param string $type button type for the form after processing
860 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
865 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
867 if ($backType != NULL) {
870 'name' => ts('Previous'),
873 if ($nextType != NULL) {
880 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
882 $buttons[] = $nextButton;
884 $this->addButtons($buttons);
887 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
889 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
890 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
892 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
893 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
898 * Adds a select based on field metadata
899 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
900 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
901 * @param $name - field name to go on the form
902 * @param array $props - mix of html attributes and special properties, namely
903 * - entity (api entity name, can usually be inferred automatically from the form class)
904 * - field (field name - only needed if different from name used on the form)
905 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
906 * - placeholder - set to NULL to disable
907 * @param bool $required
908 * @throws CRM_Core_Exception
909 * @return HTML_QuickForm_Element
911 function addSelect($name, $props = array(), $required = FALSE) {
912 if (!isset($props['entity'])) {
913 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
915 if (!isset($props['field'])) {
916 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
918 $info = civicrm_api3($props['entity'], 'getoptions', array(
919 'field' => $props['field'],
920 'options' => array('metadata' => array('fields'))
923 $options = $info['values'];
924 if (!array_key_exists('placeholder', $props)) {
925 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
927 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
928 $options = array('' => '') +
$options;
930 // Handle custom field
931 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
932 list(, $id) = explode('_', $name);
933 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
934 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
935 $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);
939 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
941 $uniqueName === $props['field'] ||
942 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
943 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
948 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
949 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
951 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
952 $props['data-api-entity'] = $props['entity'];
953 $props['data-api-field'] = $props['field'];
954 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
955 return $this->add('select', $name, $label, $options, $required, $props);
959 * Add a widget for selecting/editing/creating/copying a profile form
961 * @param string $name HTML form-element name
962 * @param string $label Printable label
963 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
964 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
965 * @param array $entities
967 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
969 // FIXME: Instead of adhoc serialization, use a single json_encode()
970 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
971 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
972 $this->add('text', $name, $label, array(
973 'class' => 'crm-profile-selector',
974 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
975 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
976 'data-entities' => json_encode($entities),
980 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
981 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
982 // 2. Based on the option, initialise proper editor
983 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
986 $editor = strtolower(CRM_Utils_Array
::value($editorID,
987 CRM_Core_OptionGroup
::values('wysiwyg_editor')
989 if (!$editor ||
$forceTextarea) {
990 $editor = 'textarea';
992 if ($editor == 'joomla default editor') {
993 $editor = 'joomlaeditor';
996 if ($editor == 'drupal default editor') {
997 $editor = 'drupalwysiwyg';
1000 //lets add the editor as a attribute
1001 $attributes['editor'] = $editor;
1003 $this->addElement($editor, $name, $label, $attributes);
1004 $this->assign('editor', $editor);
1006 // include wysiwyg editor js files
1007 // FIXME: This code does not make any sense
1008 $includeWysiwygEditor = FALSE;
1009 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1010 if (!$includeWysiwygEditor) {
1011 $includeWysiwygEditor = TRUE;
1012 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1015 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1018 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1019 $this->addElement('select', $id, $title,
1021 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1024 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1028 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1030 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1033 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1037 public function getRootTitle() {
1041 public function getCompleteTitle() {
1042 return $this->getRootTitle() . $this->getTitle();
1045 static function &getTemplate() {
1046 return self
::$_template;
1049 function addUploadElement($elementName) {
1050 $uploadNames = $this->get('uploadNames');
1051 if (!$uploadNames) {
1052 $uploadNames = array();
1054 if (is_array($elementName)) {
1055 foreach ($elementName as $name) {
1056 if (!in_array($name, $uploadNames)) {
1057 $uploadNames[] = $name;
1062 if (!in_array($elementName, $uploadNames)) {
1063 $uploadNames[] = $elementName;
1066 $this->set('uploadNames', $uploadNames);
1068 $config = CRM_Core_Config
::singleton();
1069 if (!empty($uploadNames)) {
1070 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1074 function buttonType() {
1075 $uploadNames = $this->get('uploadNames');
1076 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1077 $this->assign('buttonType', $buttonType);
1081 function getVar($name) {
1082 return isset($this->$name) ?
$this->$name : NULL;
1085 function setVar($name, $value) {
1086 $this->$name = $value;
1090 * Function to add date
1091 * @param string $name name of the element
1092 * @param string $label label of the element
1093 * @param array $attributes key / value pair
1096 * $attributes = array ( 'addTime' => true,
1097 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1099 * @param boolean $required true if required
1102 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1103 if (!empty($attributes['formatType'])) {
1104 // get actual format
1105 $params = array('name' => $attributes['formatType']);
1108 // cache date information
1110 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1111 if (empty($dateFormat[$key])) {
1112 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1113 $dateFormat[$key] = $values;
1116 $values = $dateFormat[$key];
1119 if ($values['date_format']) {
1120 $attributes['format'] = $values['date_format'];
1123 if (!empty($values['time_format'])) {
1124 $attributes['timeFormat'] = $values['time_format'];
1126 $attributes['startOffset'] = $values['start'];
1127 $attributes['endOffset'] = $values['end'];
1130 $config = CRM_Core_Config
::singleton();
1131 if (empty($attributes['format'])) {
1132 $attributes['format'] = $config->dateInputFormat
;
1135 if (!isset($attributes['startOffset'])) {
1136 $attributes['startOffset'] = 10;
1139 if (!isset($attributes['endOffset'])) {
1140 $attributes['endOffset'] = 10;
1143 $this->add('text', $name, $label, $attributes);
1145 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1147 if (!isset($attributes['timeFormat'])) {
1148 $timeFormat = $config->timeInputFormat
;
1151 $timeFormat = $attributes['timeFormat'];
1154 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1156 $show24Hours = TRUE;
1157 if ($timeFormat == 1) {
1158 $show24Hours = FALSE;
1161 //CRM-6664 -we are having time element name
1162 //in either flat string or an array format.
1163 $elementName = $name . '_time';
1164 if (substr($name, -1) == ']') {
1165 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1168 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1173 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1174 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1175 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1181 * Function that will add date and time
1183 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1184 $addTime = array('addTime' => TRUE);
1185 if (is_array($attributes)) {
1186 $attributes = array_merge($attributes, $addTime);
1189 $attributes = $addTime;
1192 $this->addDate($name, $label, $required, $attributes);
1196 * add a currency and money element to the form
1198 function addMoney($name,
1202 $addCurrency = TRUE,
1203 $currencyName = 'currency',
1204 $defaultCurrency = NULL,
1205 $freezeCurrency = FALSE
1207 $element = $this->add('text', $name, $label, $attributes, $required);
1208 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1211 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1218 * add currency element to the form
1220 function addCurrency($name = 'currency',
1223 $defaultCurrency = NULL,
1224 $freezeCurrency = FALSE
1226 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1227 $options = array('class' => 'crm-select2 eight');
1229 $currencies = array('' => '') +
$currencies;
1230 $options['placeholder'] = ts('- none -');
1232 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1233 if ($freezeCurrency) {
1236 if (!$defaultCurrency) {
1237 $config = CRM_Core_Config
::singleton();
1238 $defaultCurrency = $config->defaultCurrency
;
1240 $this->setDefaults(array($name => $defaultCurrency));
1244 * Create a single or multiple entity ref field
1245 * @param string $name
1246 * @param string $label
1247 * @param array $props mix of html and widget properties, including:
1248 * - select - params to give to select2 widget
1249 * - entity - defaults to contact
1250 * - create - can the user create a new entity on-the-fly?
1251 * Set to TRUE if entity is contact and you want the default profiles,
1252 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1253 * - api - array of settings for the getlist api
1254 * - placeholder - string
1256 * - class, etc. - other html properties
1257 * @param bool $required
1260 * @return HTML_QuickForm_Element
1262 function addEntityRef($name, $label, $props = array(), $required = FALSE) {
1263 $config = CRM_Core_Config
::singleton();
1264 // Default properties
1265 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1266 $props['entity'] = CRM_Utils_Array
::value('entity', $props, 'contact');
1268 $props['class'] = isset($props['class']) ?
$props['class'] . ' ' : '';
1269 $props['class'] .= "crm-select2 crm-form-entityref";
1271 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1272 unset($props['create']);
1274 // Convenient shortcut to passing in array create links
1275 if ($props['entity'] == 'contact' && isset($props['create']) && $props['create'] === TRUE) {
1276 if (empty($props['api']['params']['contact_type'])) {
1277 $props['create'] = CRM_Core_BAO_UFGroup
::getCreateLinks(array('new_individual', 'new_organization', 'new_household'));
1280 $props['create'] = CRM_Core_BAO_UFGroup
::getCreateLinks('new_' . strtolower($props['api']['params']['contact_type']));
1285 'minimumInputLength' => 1,
1286 'multiple' => !empty($props['multiple']),
1287 'placeholder' => CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select -') : ts('- none -')),
1288 'allowClear' => !$required,
1290 if ($props['entity'] == 'contact') {
1291 $defaults['formatInputTooShort'] = $config->includeEmailInName ?
ts('Start typing a name or email...') : ts('Start typing a name...');
1293 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1295 $this->formatReferenceFieldAttributes($props);
1296 return $this->add('text', $name, $label, $props, $required);
1302 private function formatReferenceFieldAttributes(&$props) {
1303 $props['data-select-params'] = json_encode($props['select']);
1304 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1305 $props['data-api-entity'] = $props['entity'];
1306 if (!empty($props['create'])) {
1307 $props['data-create-links'] = json_encode($props['create']);
1309 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'placeholder', 'create');
1313 * Convert all date fields within the params to mysql date ready for the
1314 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1315 * and if time is defined it is incorporated
1317 * @param array $params input params from the form
1319 * @todo it would probably be better to work on $this->_params than a passed array
1320 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1323 function convertDateFieldsToMySQL(&$params){
1324 foreach ($this->_dateFields
as $fieldName => $specs){
1325 if(!empty($params[$fieldName])){
1326 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1327 CRM_Utils_Date
::processDate(
1328 $params[$fieldName],
1329 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1333 if(isset($specs['default'])){
1334 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1340 function removeFileRequiredRules($elementName) {
1341 $this->_required
= array_diff($this->_required
, array($elementName));
1342 if (isset($this->_rules
[$elementName])) {
1343 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1344 if ($ruleInfo['type'] == 'uploadedfile') {
1345 unset($this->_rules
[$elementName][$index]);
1348 if (empty($this->_rules
[$elementName])) {
1349 unset($this->_rules
[$elementName]);
1355 * Function that can be defined in Form to override or
1356 * perform specific action on cancel action
1360 function cancelAction() {}
1363 * Helper function to verify that required fields have been filled
1364 * Typically called within the scope of a FormRule function
1366 static function validateMandatoryFields($fields, $values, &$errors) {
1367 foreach ($fields as $name => $fld) {
1368 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1369 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1375 * Get contact if for a form object. Prioritise
1376 * - cid in URL if 0 (on behalf on someoneelse)
1377 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1378 * - logged in user id if it matches the one in the cid in the URL
1379 * - contact id validated from a checksum from a checksum
1380 * - cid from the url if the caller has ACL permission to view
1381 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1383 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1385 function getContactID() {
1386 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1387 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1388 $tempID = $this->_params
['select_contact_id'];
1390 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1391 // event form stores as an indexed array, contribution form not so much...
1392 $tempID = $this->_params
[0]['select_contact_id'];
1395 // force to ignore the authenticated user
1396 if ($tempID === '0' ||
$tempID === 0) {
1397 // we set the cid on the form so that this will be retained for the Confirm page
1398 // in the multi-page form & prevent us returning the $userID when this is called
1400 // we don't really need to set it when $tempID is set because the params have that stored
1401 $this->set('cid', 0);
1405 $userID = $this->getLoggedInUserContactID();
1407 if ($tempID == $userID) {
1411 //check if this is a checksum authentication
1412 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1413 if ($userChecksum) {
1414 //check for anonymous user.
1415 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1420 // check if user has permission, CRM-12062
1421 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1429 * Get the contact id of the logged in user
1431 function getLoggedInUserContactID() {
1432 // check if the user is logged in and has a contact ID
1433 $session = CRM_Core_Session
::singleton();
1434 return $session->get('userID');
1438 * add autoselector field -if user has permission to view contacts
1439 * If adding this to a form you also need to add to the tpl e.g
1441 * {if !empty($selectable)}
1442 * <div class="crm-summary-row">
1443 * <div class="crm-label">{$form.select_contact.label}</div>
1444 * <div class="crm-content">
1445 * {$form.select_contact.html}
1449 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1450 * @param array $field metadata of field to use as selector including
1453 * - url (for ajax lookup)
1455 * @todo add data attributes so we can deal with multiple instances on a form
1457 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1458 $autoCompleteField = array_merge(array(
1459 'name_field' => 'select_contact',
1460 'id_field' => 'select_contact_id',
1461 'field_text' => ts('Select Contact'),
1462 'show_hide' => TRUE,
1463 'show_text' => ts('to select someone already in our database.'),
1464 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
1465 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
1466 'max' => civicrm_api3('setting', 'getvalue', array(
1467 'name' => 'search_autocomplete_count',
1468 'group' => 'Search Preferences',
1470 ), $autoCompleteField);
1472 if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
1473 $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
1474 $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
1475 $this->assign('selectable', $autoCompleteField['id_field']);
1477 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
1479 'form' => array('autocompletes' => $autoCompleteField),
1480 'ids' => array('profile' => $profiles),
1486 * Add the options appropriate to cid = zero - ie. autocomplete
1488 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1489 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1490 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1491 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1493 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1494 $this->assign('nocid', TRUE);
1495 $profiles = array();
1496 if($this->_values
['custom_pre_id']) {
1497 $profiles[] = $this->_values
['custom_pre_id'];
1499 if($this->_values
['custom_post_id']) {
1500 $profiles[] = $this->_values
['custom_post_id'];
1502 if($onlinePaymentProcessorEnabled) {
1503 $profiles[] = 'billing';
1505 if(!empty($this->_values
)) {
1506 $this->addAutoSelector($profiles);
1511 * Set default values on form for given contact (or no contact defaults)
1512 * @param mixed $profile_id (can be id, or profile name)
1513 * @param integer $contactID
1515 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1517 $defaults = civicrm_api3('profile', 'getsingle', array(
1518 'profile_id' => (array) $profile_id,
1519 'contact_id' => $contactID,
1523 catch (Exception
$e) {
1524 // the try catch block gives us silent failure -not 100% sure this is a good idea
1525 // as silent failures are often worse than noisy ones