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=6)
106 public $ajaxResponse = array();
109 * constants for attributes for various form elements
110 * attempt to standardize on the number of variations that we
111 * use of the below form elements
115 CONST ATTR_SPACING
= ' ';
118 * All checkboxes are defined with a common prefix. This allows us to
119 * have the same javascript to check / clear all the checkboxes etc
120 * If u have multiple groups of checkboxes, you will need to give them different
121 * ids to avoid potential name collision
123 * @var const string / int
125 CONST CB_PREFIX
= 'mark_x_', CB_PREFIY
= 'mark_y_', CB_PREFIZ
= 'mark_z_', CB_PREFIX_LEN
= 7;
128 * Constructor for the basic form page
130 * We should not use QuickForm directly. This class provides a lot
131 * of default convenient functions, rules and buttons
133 * @param object $state State associated with this form
134 * @param enum $action The mode the form is operating in (None/Create/View/Update/Delete)
135 * @param string $method The type of http method used (GET/POST)
136 * @param string $name The name of the form if different from class name
141 function __construct(
143 $action = CRM_Core_Action
::NONE
,
149 $this->_name
= $name;
152 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
155 $this->HTML_QuickForm_Page($this->_name
, $method);
157 $this->_state
=& $state;
159 $this->_state
->setName($this->_name
);
161 $this->_action
= (int) $action;
163 $this->registerRules();
165 // let the constructor initialize this, should happen only once
166 if (!isset(self
::$_template)) {
167 self
::$_template = CRM_Core_Smarty
::singleton();
170 $this->assign('snippet', (int) CRM_Utils_Array
::value('snippet', $_REQUEST));
173 static function generateID() {
177 * register all the standard rules that most forms potentially use
183 function registerRules() {
184 static $rules = array(
185 'title', 'longTitle', 'variable', 'qfVariable',
186 'phone', 'integer', 'query',
188 'domain', 'numberOfDigit',
189 'date', 'currentDate',
190 'asciiFile', 'htmlFile', 'utf8File',
191 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
192 'xssString', 'fileExists', 'autocomplete', 'validContact',
195 foreach ($rules as $rule) {
196 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
201 * Simple easy to use wrapper around addElement. Deal with
202 * simple validation rules
204 * @param string type of html element to be added
205 * @param string name of the html element
206 * @param string display label for the html element
207 * @param string attributes used for this element.
208 * These are not default values
209 * @param bool is this a required field
211 * @return object html element, could be an error object
215 function &add($type, $name, $label = '',
216 $attributes = '', $required = FALSE, $javascript = NULL
218 $element = $this->addElement($type, $name, $label, $attributes, $javascript);
219 if (HTML_QuickForm
::isError($element)) {
220 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
224 if ($type == 'file') {
225 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
228 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
230 if (HTML_QuickForm
::isError($error)) {
231 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
239 * This function is called before buildForm. Any pre-processing that
240 * needs to be done for buildForm should be done here
242 * This is a virtual function and should be redefined if needed
249 function preProcess() {}
252 * This function is called after the form is validated. Any
253 * processing of form state etc should be done in this function.
254 * Typically all processing associated with a form should be done
255 * here and relevant state should be stored in the session
257 * This is a virtual function and should be redefined if needed
264 function postProcess() {}
267 * This function is just a wrapper, so that we can call all the hook functions
269 function mainProcess() {
270 $this->postProcess();
271 $this->postProcessHook();
273 // Respond with JSON if in AJAX context
274 if (!empty($_REQUEST['snippet']) && $_REQUEST['snippet'] == CRM_Core_Smarty
::PRINT_JSON
) {
275 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
276 $this->ajaxResponse
['action'] = $this->_action
;
277 if (isset($this->_id
) ||
isset($this->id
)) {
278 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
280 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
285 * The postProcess hook is typically called by the framework
286 * However in a few cases, the form exits or redirects early in which
287 * case it needs to call this function so other modules can do the needful
288 * Calling this function directly should be avoided if possible. In general a
289 * better way is to do setUserContext so the framework does the redirect
292 function postProcessHook() {
293 CRM_Utils_Hook
::postProcess(get_class($this), $this);
297 * This virtual function is used to build the form. It replaces the
298 * buildForm associated with QuickForm_Page. This allows us to put
299 * preProcess in front of the actual form building routine
306 function buildQuickForm() {}
309 * This virtual function is used to set the default values of
310 * various form elements
314 * @return array reference to the array of default values
317 function setDefaultValues() {}
320 * This is a virtual function that adds group and global rules to
321 * the form. Keeping it distinct from the form to keep code small
322 * and localized in the form building code
329 function addRules() {}
331 function validate() {
332 $error = parent
::validate();
334 $hookErrors = CRM_Utils_Hook
::validate(
336 $this->_submitValues
,
341 if (!is_array($hookErrors)) {
342 $hookErrors = array();
345 CRM_Utils_Hook
::validateForm(
347 $this->_submitValues
,
353 if (!empty($hookErrors)) {
354 $this->_errors +
= $hookErrors;
357 return (0 == count($this->_errors
));
361 * Core function that builds the form. We redefine this function
362 * here and expect all CRM forms to build their form in the function
366 function buildForm() {
367 $this->_formBuilt
= TRUE;
371 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
374 $this->controller
->_key
&&
375 $this->controller
->_generateQFKey
377 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
378 $this->assign('qfKey', $this->controller
->_key
);
382 // _generateQFKey suppresses the qfKey generation on form snippets that
383 // are part of other forms, hence we use that to avoid adding entryURL
384 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
385 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
388 $this->buildQuickForm();
390 $defaults = $this->setDefaultValues();
391 unset($defaults['qfKey']);
393 if (!empty($defaults)) {
394 $this->setDefaults($defaults);
397 // call the form hook
398 // also call the hook function so any modules can set thier own custom defaults
399 // the user can do both the form and set default values with this hook
400 CRM_Utils_Hook
::buildForm(get_class($this), $this);
406 * Add default Next / Back buttons
408 * @param array array of associative arrays in the order in which the buttons should be
409 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
410 * The base form class will define a bunch of static arrays for commonly used
418 function addButtons($params) {
421 foreach ($params as $button) {
422 $js = CRM_Utils_Array
::value('js', $button);
423 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
425 $attrs = array('class' => 'form-submit default');
428 $attrs = array('class' => 'form-submit');
432 $attrs = array_merge($js, $attrs);
435 if ($button['type'] === 'reset') {
436 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
439 if (CRM_Utils_Array
::value('subName', $button)) {
440 $buttonName = $this->getButtonName($button['type'], $button['subName']);
443 $buttonName = $this->getButtonName($button['type']);
446 if (in_array($button['type'], array(
447 'next', 'upload')) && $button['name'] === 'Save') {
448 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
450 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
452 if (CRM_Utils_Array
::value('isDefault', $button)) {
453 $this->setDefaultAction($button['type']);
456 // if button type is upload, set the enctype
457 if ($button['type'] == 'upload') {
458 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
459 $this->setMaxFileSize();
462 // hack - addGroup uses an array to express variable spacing, read from the last element
463 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
465 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
469 * getter function for Name
479 * getter function for State
484 function &getState() {
485 return $this->_state
;
489 * getter function for StateType
494 function getStateType() {
495 return $this->_state
->getType();
499 * getter function for title. Should be over-ridden by derived class
504 function getTitle() {
505 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
509 * setter function for title.
511 * @param string $title the title of the form
516 function setTitle($title) {
517 $this->_title
= $title;
521 * Setter function for options
528 function setOptions($options) {
529 $this->_options
= $options;
533 * getter function for link.
539 $config = CRM_Core_Config
::singleton();
540 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
541 '_qf_' . $this->_name
. '_display=true'
546 * boolean function to determine if this is a one form page
551 function isSimpleForm() {
552 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
556 * getter function for Form Action
561 function getFormAction() {
562 return $this->_attributes
['action'];
566 * setter function for Form Action
573 function setFormAction($action) {
574 $this->_attributes
['action'] = $action;
578 * render form and return contents
583 function toSmarty() {
584 $renderer = $this->getRenderer();
585 $this->accept($renderer);
586 $content = $renderer->toArray();
587 $content['formName'] = $this->getName();
592 * getter function for renderer. If renderer is not set
593 * create one and initialize it
598 function &getRenderer() {
599 if (!isset($this->_renderer
)) {
600 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
602 return $this->_renderer
;
606 * Use the form name to create the tpl file name
611 function getTemplateFileName() {
612 $ext = CRM_Extension_System
::singleton()->getMapper();
613 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
614 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
615 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
618 $tplname = str_replace('_',
620 CRM_Utils_System
::getClassName($this)
627 * A wrapper for getTemplateFileName that includes calling the hook to
628 * prevent us from having to copy & paste the logic of calling the hook
630 function getHookedTemplateFileName() {
631 $pageTemplateFile = $this->getTemplateFileName();
632 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
633 return $pageTemplateFile;
637 * Default extra tpl file basically just replaces .tpl with .extra.tpl
638 * i.e. we dont override
643 function overrideExtraTemplateFileName() {
648 * Error reporting mechanism
650 * @param string $message Error Message
651 * @param int $code Error Code
652 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
657 function error($message, $code = NULL, $dao = NULL) {
659 $dao->query('ROLLBACK');
662 $error = CRM_Core_Error
::singleton();
664 $error->push($code, $message);
668 * Store the variable with the value in the form scope
670 * @param string name : name of the variable
671 * @param mixed value : value of the variable
678 function set($name, $value) {
679 $this->controller
->set($name, $value);
683 * Get the variable from the form scope
685 * @param string name : name of the variable
692 function get($name) {
693 return $this->controller
->get($name);
702 function getAction() {
703 return $this->_action
;
709 * @param int $action the mode we want to set the form
714 function setAction($action) {
715 $this->_action
= $action;
719 * assign value to name in template
721 * @param array|string $name name of variable
722 * @param mixed $value value of varaible
727 function assign($var, $value = NULL) {
728 self
::$_template->assign($var, $value);
732 * assign value to name in template by reference
734 * @param array|string $name name of variable
735 * @param mixed $value value of varaible
740 function assign_by_ref($var, &$value) {
741 self
::$_template->assign_by_ref($var, $value);
745 * appends values to template variables
747 * @param array|string $tpl_var the template variable name(s)
748 * @param mixed $value the value to append
751 function append($tpl_var, $value=NULL, $merge=FALSE) {
752 self
::$_template->append($tpl_var, $value, $merge);
756 * Returns an array containing template variables
758 * @param string $name
759 * @param string $type
762 function get_template_vars($name=null) {
763 return self
::$_template->get_template_vars($name);
766 function &addRadio($name, $title, &$values, $attributes = NULL, $separator = NULL, $required = FALSE) {
768 if (empty($attributes)) {
769 $attributes = array('id_suffix' => $name);
772 $attributes = array_merge($attributes, array('id_suffix' => $name));
774 foreach ($values as $key => $var) {
775 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
777 $group = $this->addGroup($options, $name, $title, $separator);
779 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
784 function addYesNo($id, $title, $dontKnow = NULL, $required = NULL, $attribute = NULL) {
785 if (empty($attribute)) {
786 $attribute = array('id_suffix' => $id);
789 $attribute = array_merge($attribute, array('id_suffix' => $id));
792 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attribute);
793 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attribute);
795 $choice[] = $this->createElement('radio', NULL, '22', ts("Don't Know"), '2', $attribute);
797 $this->addGroup($choice, $id, $title);
800 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
804 function addCheckBox($id, $title, $values, $other = NULL,
805 $attributes = NULL, $required = NULL,
806 $javascriptMethod = NULL,
807 $separator = '<br />', $flipValues = FALSE
811 if ($javascriptMethod) {
812 foreach ($values as $key => $var) {
814 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
817 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
822 foreach ($values as $key => $var) {
824 $options[] = $this->createElement('checkbox', $var, NULL, $key);
827 $options[] = $this->createElement('checkbox', $key, NULL, $var);
832 $this->addGroup($options, $id, $title, $separator);
835 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
840 ts('%1 is a required field.', array(1 => $title)),
846 function resetValues() {
847 $data = $this->controller
->container();
848 $data['values'][$this->_name
] = array();
852 * simple shell that derived classes can call to add buttons to
853 * the form with a customized title for the main Submit
855 * @param string $title title of the main button
856 * @param string $type button type for the form after processing
857 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
862 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
864 if ($backType != NULL) {
867 'name' => ts('Previous'),
870 if ($nextType != NULL) {
877 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
879 $buttons[] = $nextButton;
881 $this->addButtons($buttons);
884 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
886 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
887 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
889 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
890 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
894 function addSelect($name, $label, $prefix = NULL, $required = NULL, $extra = NULL, $select = '- select -') {
896 $this->addElement('select', $name . '_id' . $prefix, $label,
898 '' => $select) + CRM_Core_OptionGroup
::values($name), $extra
901 $this->addRule($name . '_id' . $prefix, ts('Please select %1', array(1 => $label)), 'required');
905 $this->addElement('select', $name . '_id', $label,
907 '' => $select) + CRM_Core_OptionGroup
::values($name), $extra
910 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
916 * Add a widget for selecting/editing/creating/copying a profile form
918 * @param string $name HTML form-element name
919 * @param string $label Printable label
920 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
921 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
922 * @param array $entities
924 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
926 // FIXME: Instead of adhoc serialization, use a single json_encode()
927 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
928 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
929 $this->add('text', $name, $label, array(
930 'class' => 'crm-profile-selector',
931 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
932 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
933 'data-entities' => json_encode($entities),
937 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
938 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
939 // 2. Based on the option, initialise proper editor
940 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
943 $editor = strtolower(CRM_Utils_Array
::value($editorID,
944 CRM_Core_OptionGroup
::values('wysiwyg_editor')
946 if (!$editor ||
$forceTextarea) {
947 $editor = 'textarea';
949 if ($editor == 'joomla default editor') {
950 $editor = 'joomlaeditor';
953 if ($editor == 'drupal default editor') {
954 $editor = 'drupalwysiwyg';
957 //lets add the editor as a attribute
958 $attributes['editor'] = $editor;
960 $this->addElement($editor, $name, $label, $attributes);
961 $this->assign('editor', $editor);
963 // include wysiwyg editor js files
964 $includeWysiwygEditor = FALSE;
965 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
966 if (!$includeWysiwygEditor) {
967 $includeWysiwygEditor = TRUE;
968 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
971 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
974 function addCountry($id, $title, $required = NULL, $extra = NULL) {
975 $this->addElement('select', $id, $title,
977 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
980 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
984 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
986 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
989 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
993 function buildAddressBlock($locationId, $title, $phone,
994 $alternatePhone = NULL, $addressRequired = NULL,
995 $phoneRequired = NULL, $altPhoneRequired = NULL,
998 if (!$locationName) {
999 $locationName = "location";
1002 $config = CRM_Core_Config
::singleton();
1003 $attributes = CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Address');
1005 $location[$locationId]['address']['street_address'] = $this->addElement('text', "{$locationName}[$locationId][address][street_address]", $title,
1006 $attributes['street_address']
1008 if ($addressRequired) {
1009 $this->addRule("{$locationName}[$locationId][address][street_address]", ts("Please enter the Street Address for %1.", array(1 => $title)), 'required');
1012 $location[$locationId]['address']['supplemental_address_1'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_1]", ts('Supplemental Address 1'),
1013 $attributes['supplemental_address_1']
1015 $location[$locationId]['address']['supplemental_address_2'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_2]", ts('Supplemental Address 2'),
1016 $attributes['supplemental_address_2']
1019 $location[$locationId]['address']['city'] = $this->addElement('text', "{$locationName}[$locationId][address][city]", ts('City'),
1022 if ($addressRequired) {
1023 $this->addRule("{$locationName}[$locationId][address][city]", ts("Please enter the City for %1.", array(1 => $title)), 'required');
1026 $location[$locationId]['address']['postal_code'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code]", ts('Zip / Postal Code'),
1027 $attributes['postal_code']
1029 if ($addressRequired) {
1030 $this->addRule("{$locationName}[$locationId][address][postal_code]", ts("Please enter the Zip/Postal Code for %1.", array(1 => $title)), 'required');
1033 $location[$locationId]['address']['postal_code_suffix'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code_suffix]", ts('Add-on Code'),
1034 array('size' => 4, 'maxlength' => 12)
1036 $this->addRule("{$locationName}[$locationId][address][postal_code_suffix]", ts('Zip-Plus not valid.'), 'positiveInteger');
1038 if ($config->includeCounty
) {
1039 $location[$locationId]['address']['county_id'] = $this->addElement('select', "{$locationName}[$locationId][address][county_id]", ts('County'),
1040 array('' => ts('- select -')) + CRM_Core_PseudoConstant
::county()
1044 $location[$locationId]['address']['state_province_id'] = $this->addElement('select', "{$locationName}[$locationId][address][state_province_id]", ts('State / Province'),
1045 array('' => ts('- select -')) + CRM_Core_PseudoConstant
::stateProvince()
1048 $location[$locationId]['address']['country_id'] = $this->addElement('select', "{$locationName}[$locationId][address][country_id]", ts('Country'),
1049 array('' => ts('- select -')) + CRM_Core_PseudoConstant
::country()
1051 if ($addressRequired) {
1052 $this->addRule("{$locationName}[$locationId][address][country_id]", ts("Please select the Country for %1.", array(1 => $title)), 'required');
1057 $location[$locationId]['phone'][1]['phone'] = $this->addElement('text',
1058 "{$locationName}[$locationId][phone][1][phone]",
1060 CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Phone',
1064 if ($phoneRequired) {
1065 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a value for %1', array(1 => $phone)), 'required');
1067 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a valid number for %1', array(1 => $phone)), 'phone');
1070 if ($alternatePhone) {
1071 $location[$locationId]['phone'][2]['phone'] = $this->addElement('text',
1072 "{$locationName}[$locationId][phone][2][phone]",
1074 CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Phone',
1078 if ($alternatePhoneRequired) {
1079 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a value for %1', array(1 => $alternatePhone)), 'required');
1081 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a valid number for %1', array(1 => $alternatePhone)), 'phone');
1085 public function getRootTitle() {
1089 public function getCompleteTitle() {
1090 return $this->getRootTitle() . $this->getTitle();
1093 static function &getTemplate() {
1094 return self
::$_template;
1097 function addUploadElement($elementName) {
1098 $uploadNames = $this->get('uploadNames');
1099 if (!$uploadNames) {
1100 $uploadNames = array();
1102 if (is_array($elementName)) {
1103 foreach ($elementName as $name) {
1104 if (!in_array($name, $uploadNames)) {
1105 $uploadNames[] = $name;
1110 if (!in_array($elementName, $uploadNames)) {
1111 $uploadNames[] = $elementName;
1114 $this->set('uploadNames', $uploadNames);
1116 $config = CRM_Core_Config
::singleton();
1117 if (!empty($uploadNames)) {
1118 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1122 function buttonType() {
1123 $uploadNames = $this->get('uploadNames');
1124 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1125 $this->assign('buttonType', $buttonType);
1129 function getVar($name) {
1130 return isset($this->$name) ?
$this->$name : NULL;
1133 function setVar($name, $value) {
1134 $this->$name = $value;
1138 * Function to add date
1139 * @param string $name name of the element
1140 * @param string $label label of the element
1141 * @param array $attributes key / value pair
1144 * $attributes = array ( 'addTime' => true,
1145 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1147 * @param boolean $required true if required
1150 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1151 if (CRM_Utils_Array
::value('formatType', $attributes)) {
1152 // get actual format
1153 $params = array('name' => $attributes['formatType']);
1156 // cache date information
1158 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1159 if (!CRM_Utils_Array
::value($key, $dateFormat)) {
1160 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1161 $dateFormat[$key] = $values;
1164 $values = $dateFormat[$key];
1167 if ($values['date_format']) {
1168 $attributes['format'] = $values['date_format'];
1171 if (CRM_Utils_Array
::value('time_format', $values)) {
1172 $attributes['timeFormat'] = $values['time_format'];
1174 $attributes['startOffset'] = $values['start'];
1175 $attributes['endOffset'] = $values['end'];
1178 $config = CRM_Core_Config
::singleton();
1179 if (!CRM_Utils_Array
::value('format', $attributes)) {
1180 $attributes['format'] = $config->dateInputFormat
;
1183 if (!isset($attributes['startOffset'])) {
1184 $attributes['startOffset'] = 10;
1187 if (!isset($attributes['endOffset'])) {
1188 $attributes['endOffset'] = 10;
1191 $this->add('text', $name, $label, $attributes);
1193 if (CRM_Utils_Array
::value('addTime', $attributes) ||
1194 CRM_Utils_Array
::value('timeFormat', $attributes)
1197 if (!isset($attributes['timeFormat'])) {
1198 $timeFormat = $config->timeInputFormat
;
1201 $timeFormat = $attributes['timeFormat'];
1204 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1206 $show24Hours = TRUE;
1207 if ($timeFormat == 1) {
1208 $show24Hours = FALSE;
1211 //CRM-6664 -we are having time element name
1212 //in either flat string or an array format.
1213 $elementName = $name . '_time';
1214 if (substr($name, -1) == ']') {
1215 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1218 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1223 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1224 if (CRM_Utils_Array
::value('addTime', $attributes) && CRM_Utils_Array
::value('addTimeRequired', $attributes)) {
1225 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1231 * Function that will add date and time
1233 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1234 $addTime = array('addTime' => TRUE);
1235 if (is_array($attributes)) {
1236 $attributes = array_merge($attributes, $addTime);
1239 $attributes = $addTime;
1242 $this->addDate($name, $label, $required, $attributes);
1246 * add a currency and money element to the form
1248 function addMoney($name,
1252 $addCurrency = TRUE,
1253 $currencyName = 'currency',
1254 $defaultCurrency = NULL,
1255 $freezeCurrency = FALSE
1257 $element = $this->add('text', $name, $label, $attributes, $required);
1258 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1261 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1268 * add currency element to the form
1270 function addCurrency($name = 'currency',
1273 $defaultCurrency = NULL,
1274 $freezeCurrency = FALSE
1276 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1278 $currencies = array(
1279 '' => ts('- select -')) +
$currencies;
1281 $ele = $this->add('select', $name, $label, $currencies, $required);
1282 if ($freezeCurrency) {
1285 if (!$defaultCurrency) {
1286 $config = CRM_Core_Config
::singleton();
1287 $defaultCurrency = $config->defaultCurrency
;
1289 $this->setDefaults(array($name => $defaultCurrency));
1293 * Convert all date fields within the params to mysql date ready for the
1294 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1295 * and if time is defined it is incorporated
1297 * @param array $params input params from the form
1299 * @todo it would probably be better to work on $this->_params than a passed array
1300 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1303 function convertDateFieldsToMySQL(&$params){
1304 foreach ($this->_dateFields
as $fieldName => $specs){
1305 if(!empty($params[$fieldName])){
1306 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1307 CRM_Utils_Date
::processDate(
1308 $params[$fieldName],
1309 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1313 if(isset($specs['default'])){
1314 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1320 function removeFileRequiredRules($elementName) {
1321 $this->_required
= array_diff($this->_required
, array($elementName));
1322 if (isset($this->_rules
[$elementName])) {
1323 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1324 if ($ruleInfo['type'] == 'uploadedfile') {
1325 unset($this->_rules
[$elementName][$index]);
1328 if (empty($this->_rules
[$elementName])) {
1329 unset($this->_rules
[$elementName]);
1335 * Function that can be defined in Form to override or
1336 * perform specific action on cancel action
1340 function cancelAction() {}
1343 * Helper function to verify that required fields have been filled
1344 * Typically called within the scope of a FormRule function
1346 static function validateMandatoryFields($fields, $values, &$errors) {
1347 foreach ($fields as $name => $fld) {
1348 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1349 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1355 * Get contact if for a form object. Prioritise
1356 * - cid in URL if 0 (on behalf on someoneelse)
1357 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1358 * - logged in user id if it matches the one in the cid in the URL
1359 * - contact id validated from a checksum from a checksum
1360 * - cid from the url if the caller has ACL permission to view
1361 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1363 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1365 function getContactID() {
1366 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1367 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1368 $tempID = $this->_params
['select_contact_id'];
1370 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1371 // event form stores as an indexed array, contribution form not so much...
1372 $tempID = $this->_params
[0]['select_contact_id'];
1374 // force to ignore the authenticated user
1375 if ($tempID === '0') {
1379 $userID = $this->getLoggedInUserContactID();
1381 if ($tempID == $userID) {
1385 //check if this is a checksum authentication
1386 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1387 if ($userChecksum) {
1388 //check for anonymous user.
1389 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1394 // check if user has permission, CRM-12062
1395 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1403 * Get the contact id of the logged in user
1405 function getLoggedInUserContactID() {
1406 // check if the user is logged in and has a contact ID
1407 $session = CRM_Core_Session
::singleton();
1408 return $session->get('userID');
1412 * add autoselector field -if user has permission to view contacts
1413 * If adding this to a form you also need to add to the tpl e.g
1415 * {if !empty($selectable)}
1416 * <div class="crm-summary-row">
1417 * <div class="crm-label">{$form.select_contact.label}</div>
1418 * <div class="crm-content">
1419 * {$form.select_contact.html}
1423 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1424 * @param array $field metadata of field to use as selector including
1427 * - url (for ajax lookup)
1429 * @todo add data attributes so we can deal with multiple instances on a form
1431 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1432 $autoCompleteField = array_merge(array(
1433 'name_field' => 'select_contact',
1434 'id_field' => 'select_contact_id',
1435 'field_text' => ts('Select Contact'),
1436 'show_hide' => TRUE,
1437 'show_text' => ts('to select someone already in our database.'),
1438 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
1439 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
1440 'max' => civicrm_api3('setting', 'getvalue', array(
1441 'name' => 'search_autocomplete_count',
1442 'group' => 'Search Preferences',
1444 ), $autoCompleteField);
1446 if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
1447 $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
1448 $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
1449 $this->assign('selectable', $autoCompleteField['id_field']);
1451 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
1453 'form' => array('autocompletes' => $autoCompleteField),
1454 'ids' => array('profile' => $profiles),
1460 * Add the options appropriate to cid = zero - ie. autocomplete
1462 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1463 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1464 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1465 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1467 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1468 $this->assign('nocid', TRUE);
1469 $profiles = array();
1470 if($this->_values
['custom_pre_id']) {
1471 $profiles[] = $this->_values
['custom_pre_id'];
1473 if($this->_values
['custom_post_id']) {
1474 $profiles[] = $this->_values
['custom_post_id'];
1476 if($onlinePaymentProcessorEnabled) {
1477 $profiles[] = 'billing';
1479 if(!empty($this->_values
)) {
1480 $this->addAutoSelector($profiles);
1485 * Set default values on form for given contact (or no contact defaults)
1486 * @param mixed $profile_id (can be id, or profile name)
1487 * @param integer $contactID
1489 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1491 $defaults = civicrm_api3('profile', 'getsingle', array(
1492 'profile_id' => (array) $profile_id,
1493 'contact_id' => $contactID,
1497 catch (Exception
$e) {
1498 // the try catch block gives us silent failure -not 100% sure this is a good idea
1499 // as silent failures are often worse than noisy ones