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 * constants for attributes for various form elements
103 * attempt to standardize on the number of variations that we
104 * use of the below form elements
108 CONST ATTR_SPACING
= ' ';
111 * All checkboxes are defined with a common prefix. This allows us to
112 * have the same javascript to check / clear all the checkboxes etc
113 * If u have multiple groups of checkboxes, you will need to give them different
114 * ids to avoid potential name collision
116 * @var const string / int
118 CONST CB_PREFIX
= 'mark_x_', CB_PREFIY
= 'mark_y_', CB_PREFIZ
= 'mark_z_', CB_PREFIX_LEN
= 7;
121 * Constructor for the basic form page
123 * We should not use QuickForm directly. This class provides a lot
124 * of default convenient functions, rules and buttons
126 * @param object $state State associated with this form
127 * @param enum $action The mode the form is operating in (None/Create/View/Update/Delete)
128 * @param string $method The type of http method used (GET/POST)
129 * @param string $name The name of the form if different from class name
134 function __construct(
136 $action = CRM_Core_Action
::NONE
,
142 $this->_name
= $name;
145 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
148 $this->HTML_QuickForm_Page($this->_name
, $method);
150 $this->_state
=& $state;
152 $this->_state
->setName($this->_name
);
154 $this->_action
= (int) $action;
156 $this->registerRules();
158 // let the constructor initialize this, should happen only once
159 if (!isset(self
::$_template)) {
160 self
::$_template = CRM_Core_Smarty
::singleton();
164 static function generateID() {
168 * register all the standard rules that most forms potentially use
174 function registerRules() {
175 static $rules = array(
176 'title', 'longTitle', 'variable', 'qfVariable',
177 'phone', 'integer', 'query',
179 'domain', 'numberOfDigit',
180 'date', 'currentDate',
181 'asciiFile', 'htmlFile', 'utf8File',
182 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
183 'xssString', 'fileExists', 'autocomplete', 'validContact',
186 foreach ($rules as $rule) {
187 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
192 * Simple easy to use wrapper around addElement. Deal with
193 * simple validation rules
195 * @param string type of html element to be added
196 * @param string name of the html element
197 * @param string display label for the html element
198 * @param string attributes used for this element.
199 * These are not default values
200 * @param bool is this a required field
202 * @return object html element, could be an error object
206 function &add($type, $name, $label = '',
207 $attributes = '', $required = FALSE, $javascript = NULL
209 $element = $this->addElement($type, $name, $label, $attributes, $javascript);
210 if (HTML_QuickForm
::isError($element)) {
211 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
215 if ($type == 'file') {
216 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
219 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
221 if (HTML_QuickForm
::isError($error)) {
222 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
230 * This function is called before buildForm. Any pre-processing that
231 * needs to be done for buildForm should be done here
233 * This is a virtual function and should be redefined if needed
240 function preProcess() {}
243 * This function is called after the form is validated. Any
244 * processing of form state etc should be done in this function.
245 * Typically all processing associated with a form should be done
246 * here and relevant state should be stored in the session
248 * This is a virtual function and should be redefined if needed
255 function postProcess() {}
258 * This function is just a wrapper, so that we can call all the hook functions
260 function mainProcess() {
261 $this->postProcess();
263 $this->postProcessHook();
267 * The postProcess hook is typically called by the framework
268 * However in a few cases, the form exits or redirects early in which
269 * case it needs to call this function so other modules can do the needful
270 * Calling this function directly should be avoided if possible. In general a
271 * better way is to do setUserContext so the framework does the redirect
274 function postProcessHook() {
275 CRM_Utils_Hook
::postProcess(get_class($this), $this);
279 * This virtual function is used to build the form. It replaces the
280 * buildForm associated with QuickForm_Page. This allows us to put
281 * preProcess in front of the actual form building routine
288 function buildQuickForm() {}
291 * This virtual function is used to set the default values of
292 * various form elements
296 * @return array reference to the array of default values
299 function setDefaultValues() {}
302 * This is a virtual function that adds group and global rules to
303 * the form. Keeping it distinct from the form to keep code small
304 * and localized in the form building code
311 function addRules() {}
313 function validate() {
314 $error = parent
::validate();
316 $hookErrors = CRM_Utils_Hook
::validate(
318 $this->_submitValues
,
323 if (!is_array($hookErrors)) {
324 $hookErrors = array();
327 CRM_Utils_Hook
::validateForm(
329 $this->_submitValues
,
335 if (!empty($hookErrors)) {
336 $this->_errors +
= $hookErrors;
339 return (0 == count($this->_errors
));
343 * Core function that builds the form. We redefine this function
344 * here and expect all CRM forms to build their form in the function
348 function buildForm() {
349 $this->_formBuilt
= TRUE;
353 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
356 $this->controller
->_key
&&
357 $this->controller
->_generateQFKey
359 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
360 $this->assign('qfKey', $this->controller
->_key
);
363 if ($this->controller
->_entryURL
) {
364 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
367 $this->buildQuickForm();
369 $defaults = $this->setDefaultValues();
370 unset($defaults['qfKey']);
372 if (!empty($defaults)) {
373 $this->setDefaults($defaults);
376 // call the form hook
377 // also call the hook function so any modules can set thier own custom defaults
378 // the user can do both the form and set default values with this hook
379 CRM_Utils_Hook
::buildForm(get_class($this), $this);
385 * Add default Next / Back buttons
387 * @param array array of associative arrays in the order in which the buttons should be
388 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
389 * The base form class will define a bunch of static arrays for commonly used
397 function addButtons($params) {
400 foreach ($params as $button) {
401 $js = CRM_Utils_Array
::value('js', $button);
402 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
404 $attrs = array('class' => 'form-submit default');
407 $attrs = array('class' => 'form-submit');
411 $attrs = array_merge($js, $attrs);
414 if ($button['type'] === 'reset') {
415 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
418 if (CRM_Utils_Array
::value('subName', $button)) {
419 $buttonName = $this->getButtonName($button['type'], $button['subName']);
422 $buttonName = $this->getButtonName($button['type']);
425 if (in_array($button['type'], array(
426 'next', 'upload')) && $button['name'] === 'Save') {
427 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
429 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
431 if (CRM_Utils_Array
::value('isDefault', $button)) {
432 $this->setDefaultAction($button['type']);
435 // if button type is upload, set the enctype
436 if ($button['type'] == 'upload') {
437 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
438 $this->setMaxFileSize();
441 // hack - addGroup uses an array to express variable spacing, read from the last element
442 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
444 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
448 * getter function for Name
458 * getter function for State
463 function &getState() {
464 return $this->_state
;
468 * getter function for StateType
473 function getStateType() {
474 return $this->_state
->getType();
478 * getter function for title. Should be over-ridden by derived class
483 function getTitle() {
484 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
488 * setter function for title.
490 * @param string $title the title of the form
495 function setTitle($title) {
496 $this->_title
= $title;
500 * Setter function for options
507 function setOptions($options) {
508 $this->_options
= $options;
512 * getter function for link.
518 $config = CRM_Core_Config
::singleton();
519 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
520 '_qf_' . $this->_name
. '_display=true'
525 * boolean function to determine if this is a one form page
530 function isSimpleForm() {
531 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
535 * getter function for Form Action
540 function getFormAction() {
541 return $this->_attributes
['action'];
545 * setter function for Form Action
552 function setFormAction($action) {
553 $this->_attributes
['action'] = $action;
557 * render form and return contents
562 function toSmarty() {
563 $renderer = $this->getRenderer();
564 $this->accept($renderer);
565 $content = $renderer->toArray();
566 $content['formName'] = $this->getName();
571 * getter function for renderer. If renderer is not set
572 * create one and initialize it
577 function &getRenderer() {
578 if (!isset($this->_renderer
)) {
579 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
581 return $this->_renderer
;
585 * Use the form name to create the tpl file name
590 function getTemplateFileName() {
591 $ext = CRM_Extension_System
::singleton()->getMapper();
592 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
593 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
594 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
597 $tplname = str_replace('_',
599 CRM_Utils_System
::getClassName($this)
606 * A wrapper for getTemplateFileName that includes calling the hook to
607 * prevent us from having to copy & paste the logic of calling the hook
609 function getHookedTemplateFileName() {
610 $pageTemplateFile = $this->getTemplateFileName();
611 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
612 return $pageTemplateFile;
616 * Default extra tpl file basically just replaces .tpl with .extra.tpl
617 * i.e. we dont override
622 function overrideExtraTemplateFileName() {
627 * Error reporting mechanism
629 * @param string $message Error Message
630 * @param int $code Error Code
631 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
636 function error($message, $code = NULL, $dao = NULL) {
638 $dao->query('ROLLBACK');
641 $error = CRM_Core_Error
::singleton();
643 $error->push($code, $message);
647 * Store the variable with the value in the form scope
649 * @param string name : name of the variable
650 * @param mixed value : value of the variable
657 function set($name, $value) {
658 $this->controller
->set($name, $value);
662 * Get the variable from the form scope
664 * @param string name : name of the variable
671 function get($name) {
672 return $this->controller
->get($name);
681 function getAction() {
682 return $this->_action
;
688 * @param int $action the mode we want to set the form
693 function setAction($action) {
694 $this->_action
= $action;
698 * assign value to name in template
700 * @param array|string $name name of variable
701 * @param mixed $value value of varaible
706 function assign($var, $value = NULL) {
707 self
::$_template->assign($var, $value);
711 * assign value to name in template by reference
713 * @param array|string $name name of variable
714 * @param mixed $value value of varaible
719 function assign_by_ref($var, &$value) {
720 self
::$_template->assign_by_ref($var, $value);
723 function &addRadio($name, $title, &$values, $attributes = NULL, $separator = NULL, $required = FALSE) {
725 if (empty($attributes)) {
726 $attributes = array('id_suffix' => $name);
729 $attributes = array_merge($attributes, array('id_suffix' => $name));
731 foreach ($values as $key => $var) {
732 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
734 $group = $this->addGroup($options, $name, $title, $separator);
736 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
741 function addYesNo($id, $title, $dontKnow = NULL, $required = NULL, $attribute = NULL) {
742 if (empty($attribute)) {
743 $attribute = array('id_suffix' => $id);
746 $attribute = array_merge($attribute, array('id_suffix' => $id));
749 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attribute);
750 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attribute);
752 $choice[] = $this->createElement('radio', NULL, '22', ts("Don't Know"), '2', $attribute);
754 $this->addGroup($choice, $id, $title);
757 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
761 function addCheckBox($id, $title, $values, $other = NULL,
762 $attributes = NULL, $required = NULL,
763 $javascriptMethod = NULL,
764 $separator = '<br />', $flipValues = FALSE
768 if ($javascriptMethod) {
769 foreach ($values as $key => $var) {
771 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
774 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
779 foreach ($values as $key => $var) {
781 $options[] = $this->createElement('checkbox', $var, NULL, $key);
784 $options[] = $this->createElement('checkbox', $key, NULL, $var);
789 $this->addGroup($options, $id, $title, $separator);
792 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
797 ts('%1 is a required field.', array(1 => $title)),
803 function resetValues() {
804 $data = $this->controller
->container();
805 $data['values'][$this->_name
] = array();
809 * simple shell that derived classes can call to add buttons to
810 * the form with a customized title for the main Submit
812 * @param string $title title of the main button
813 * @param string $type button type for the form after processing
814 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
819 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
821 if ($backType != NULL) {
824 'name' => ts('Previous'),
827 if ($nextType != NULL) {
834 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
836 $buttons[] = $nextButton;
838 $this->addButtons($buttons);
841 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
843 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
844 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
846 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
847 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
851 function addSelect($name, $label, $prefix = NULL, $required = NULL, $extra = NULL, $select = '- select -') {
853 $this->addElement('select', $name . '_id' . $prefix, $label,
855 '' => $select) + CRM_Core_OptionGroup
::values($name), $extra
858 $this->addRule($name . '_id' . $prefix, ts('Please select %1', array(1 => $label)), 'required');
862 $this->addElement('select', $name . '_id', $label,
864 '' => $select) + CRM_Core_OptionGroup
::values($name), $extra
867 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
873 * Add a widget for selecting/editing/creating/copying a profile form
875 * @param string $name HTML form-element name
876 * @param string $label Printable label
877 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
878 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
879 * @param array $entities
881 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
883 // FIXME: Instead of adhoc serialization, use a single json_encode()
884 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
885 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
886 $this->add('text', $name, $label, array(
887 'class' => 'crm-profile-selector',
888 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
889 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
890 'data-entities' => json_encode($entities),
894 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
895 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
896 // 2. Based on the option, initialise proper editor
897 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
900 $editor = strtolower(CRM_Utils_Array
::value($editorID,
901 CRM_Core_OptionGroup
::values('wysiwyg_editor')
903 if (!$editor ||
$forceTextarea) {
904 $editor = 'textarea';
906 if ($editor == 'joomla default editor') {
907 $editor = 'joomlaeditor';
910 if ($editor == 'drupal default editor') {
911 $editor = 'drupalwysiwyg';
914 //lets add the editor as a attribute
915 $attributes['editor'] = $editor;
917 $this->addElement($editor, $name, $label, $attributes);
918 $this->assign('editor', $editor);
920 // include wysiwyg editor js files
921 $includeWysiwygEditor = FALSE;
922 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
923 if (!$includeWysiwygEditor) {
924 $includeWysiwygEditor = TRUE;
925 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
928 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
931 function addCountry($id, $title, $required = NULL, $extra = NULL) {
932 $this->addElement('select', $id, $title,
934 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
937 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
941 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
943 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
946 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
950 function buildAddressBlock($locationId, $title, $phone,
951 $alternatePhone = NULL, $addressRequired = NULL,
952 $phoneRequired = NULL, $altPhoneRequired = NULL,
955 if (!$locationName) {
956 $locationName = "location";
959 $config = CRM_Core_Config
::singleton();
960 $attributes = CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Address');
962 $location[$locationId]['address']['street_address'] = $this->addElement('text', "{$locationName}[$locationId][address][street_address]", $title,
963 $attributes['street_address']
965 if ($addressRequired) {
966 $this->addRule("{$locationName}[$locationId][address][street_address]", ts("Please enter the Street Address for %1.", array(1 => $title)), 'required');
969 $location[$locationId]['address']['supplemental_address_1'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_1]", ts('Supplemental Address 1'),
970 $attributes['supplemental_address_1']
972 $location[$locationId]['address']['supplemental_address_2'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_2]", ts('Supplemental Address 2'),
973 $attributes['supplemental_address_2']
976 $location[$locationId]['address']['city'] = $this->addElement('text', "{$locationName}[$locationId][address][city]", ts('City'),
979 if ($addressRequired) {
980 $this->addRule("{$locationName}[$locationId][address][city]", ts("Please enter the City for %1.", array(1 => $title)), 'required');
983 $location[$locationId]['address']['postal_code'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code]", ts('Zip / Postal Code'),
984 $attributes['postal_code']
986 if ($addressRequired) {
987 $this->addRule("{$locationName}[$locationId][address][postal_code]", ts("Please enter the Zip/Postal Code for %1.", array(1 => $title)), 'required');
990 $location[$locationId]['address']['postal_code_suffix'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code_suffix]", ts('Add-on Code'),
991 array('size' => 4, 'maxlength' => 12)
993 $this->addRule("{$locationName}[$locationId][address][postal_code_suffix]", ts('Zip-Plus not valid.'), 'positiveInteger');
995 if ($config->includeCounty
) {
996 $location[$locationId]['address']['county_id'] = $this->addElement('select', "{$locationName}[$locationId][address][county_id]", ts('County'),
997 array('' => ts('- select -')) + CRM_Core_PseudoConstant
::county()
1001 $location[$locationId]['address']['state_province_id'] = $this->addElement('select', "{$locationName}[$locationId][address][state_province_id]", ts('State / Province'),
1002 array('' => ts('- select -')) + CRM_Core_PseudoConstant
::stateProvince()
1005 $location[$locationId]['address']['country_id'] = $this->addElement('select', "{$locationName}[$locationId][address][country_id]", ts('Country'),
1006 array('' => ts('- select -')) + CRM_Core_PseudoConstant
::country()
1008 if ($addressRequired) {
1009 $this->addRule("{$locationName}[$locationId][address][country_id]", ts("Please select the Country for %1.", array(1 => $title)), 'required');
1014 $location[$locationId]['phone'][1]['phone'] = $this->addElement('text',
1015 "{$locationName}[$locationId][phone][1][phone]",
1017 CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Phone',
1021 if ($phoneRequired) {
1022 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a value for %1', array(1 => $phone)), 'required');
1024 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a valid number for %1', array(1 => $phone)), 'phone');
1027 if ($alternatePhone) {
1028 $location[$locationId]['phone'][2]['phone'] = $this->addElement('text',
1029 "{$locationName}[$locationId][phone][2][phone]",
1031 CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Phone',
1035 if ($alternatePhoneRequired) {
1036 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a value for %1', array(1 => $alternatePhone)), 'required');
1038 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a valid number for %1', array(1 => $alternatePhone)), 'phone');
1042 public function getRootTitle() {
1046 public function getCompleteTitle() {
1047 return $this->getRootTitle() . $this->getTitle();
1050 static function &getTemplate() {
1051 return self
::$_template;
1054 function addUploadElement($elementName) {
1055 $uploadNames = $this->get('uploadNames');
1056 if (!$uploadNames) {
1057 $uploadNames = array();
1059 if (is_array($elementName)) {
1060 foreach ($elementName as $name) {
1061 if (!in_array($name, $uploadNames)) {
1062 $uploadNames[] = $name;
1067 if (!in_array($elementName, $uploadNames)) {
1068 $uploadNames[] = $elementName;
1071 $this->set('uploadNames', $uploadNames);
1073 $config = CRM_Core_Config
::singleton();
1074 if (!empty($uploadNames)) {
1075 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1079 function buttonType() {
1080 $uploadNames = $this->get('uploadNames');
1081 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1082 $this->assign('buttonType', $buttonType);
1086 function getVar($name) {
1087 return isset($this->$name) ?
$this->$name : NULL;
1090 function setVar($name, $value) {
1091 $this->$name = $value;
1095 * Function to add date
1096 * @param string $name name of the element
1097 * @param string $label label of the element
1098 * @param array $attributes key / value pair
1101 * $attributes = array ( 'addTime' => true,
1102 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1104 * @param boolean $required true if required
1107 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1108 if (CRM_Utils_Array
::value('formatType', $attributes)) {
1109 // get actual format
1110 $params = array('name' => $attributes['formatType']);
1113 // cache date information
1115 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1116 if (!CRM_Utils_Array
::value($key, $dateFormat)) {
1117 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1118 $dateFormat[$key] = $values;
1121 $values = $dateFormat[$key];
1124 if ($values['date_format']) {
1125 $attributes['format'] = $values['date_format'];
1128 if (CRM_Utils_Array
::value('time_format', $values)) {
1129 $attributes['timeFormat'] = $values['time_format'];
1131 $attributes['startOffset'] = $values['start'];
1132 $attributes['endOffset'] = $values['end'];
1135 $config = CRM_Core_Config
::singleton();
1136 if (!CRM_Utils_Array
::value('format', $attributes)) {
1137 $attributes['format'] = $config->dateInputFormat
;
1140 if (!isset($attributes['startOffset'])) {
1141 $attributes['startOffset'] = 10;
1144 if (!isset($attributes['endOffset'])) {
1145 $attributes['endOffset'] = 10;
1148 $this->add('text', $name, $label, $attributes);
1150 if (CRM_Utils_Array
::value('addTime', $attributes) ||
1151 CRM_Utils_Array
::value('timeFormat', $attributes)
1154 if (!isset($attributes['timeFormat'])) {
1155 $timeFormat = $config->timeInputFormat
;
1158 $timeFormat = $attributes['timeFormat'];
1161 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1163 $show24Hours = TRUE;
1164 if ($timeFormat == 1) {
1165 $show24Hours = FALSE;
1168 //CRM-6664 -we are having time element name
1169 //in either flat string or an array format.
1170 $elementName = $name . '_time';
1171 if (substr($name, -1) == ']') {
1172 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1175 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1180 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1181 if (CRM_Utils_Array
::value('addTime', $attributes) && CRM_Utils_Array
::value('addTimeRequired', $attributes)) {
1182 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1188 * Function that will add date and time
1190 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1191 $addTime = array('addTime' => TRUE);
1192 if (is_array($attributes)) {
1193 $attributes = array_merge($attributes, $addTime);
1196 $attributes = $addTime;
1199 $this->addDate($name, $label, $required, $attributes);
1203 * add a currency and money element to the form
1205 function addMoney($name,
1209 $addCurrency = TRUE,
1210 $currencyName = 'currency',
1211 $defaultCurrency = NULL,
1212 $freezeCurrency = FALSE
1214 $element = $this->add('text', $name, $label, $attributes, $required);
1215 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1218 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1225 * add currency element to the form
1227 function addCurrency($name = 'currency',
1230 $defaultCurrency = NULL,
1231 $freezeCurrency = FALSE
1233 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1235 $currencies = array(
1236 '' => ts('- select -')) +
$currencies;
1238 $ele = $this->add('select', $name, $label, $currencies, $required);
1239 if ($freezeCurrency) {
1242 if (!$defaultCurrency) {
1243 $config = CRM_Core_Config
::singleton();
1244 $defaultCurrency = $config->defaultCurrency
;
1246 $this->setDefaults(array($name => $defaultCurrency));
1250 * Convert all date fields within the params to mysql date ready for the
1251 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1252 * and if time is defined it is incorporated
1254 * @param array $params input params from the form
1256 * @todo it would probably be better to work on $this->_params than a passed array
1257 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1260 function convertDateFieldsToMySQL(&$params){
1261 foreach ($this->_dateFields
as $fieldName => $specs){
1262 if(!empty($params[$fieldName])){
1263 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1264 CRM_Utils_Date
::processDate(
1265 $params[$fieldName],
1266 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1270 if(isset($specs['default'])){
1271 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1277 function removeFileRequiredRules($elementName) {
1278 $this->_required
= array_diff($this->_required
, array($elementName));
1279 if (isset($this->_rules
[$elementName])) {
1280 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1281 if ($ruleInfo['type'] == 'uploadedfile') {
1282 unset($this->_rules
[$elementName][$index]);
1285 if (empty($this->_rules
[$elementName])) {
1286 unset($this->_rules
[$elementName]);
1292 * Function that can be defined in Form to override or
1293 * perform specific action on cancel action
1297 function cancelAction() {}
1300 * Helper function to verify that required fields have been filled
1301 * Typically called within the scope of a FormRule function
1303 static function validateMandatoryFields($fields, $values, &$errors) {
1304 foreach ($fields as $name => $fld) {
1305 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1306 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1312 * Get contact if for a form object. Prioritise
1313 * - cid in URL if 0 (on behalf on someoneelse)
1314 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1315 * - logged in user id if it matches the one in the cid in the URL
1316 * - contact id validated from a checksum from a checksum
1317 * - cid from the url if the caller has ACL permission to view
1318 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1320 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1322 function getContactID() {
1323 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1324 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1325 $tempID = $this->_params
['select_contact_id'];
1327 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1328 // event form stores as an indexed array, contribution form not so much...
1329 $tempID = $this->_params
[0]['select_contact_id'];
1331 // force to ignore the authenticated user
1332 if ($tempID === '0') {
1336 $userID = $this->getLoggedInUserContactID();
1338 if ($tempID == $userID) {
1342 //check if this is a checksum authentication
1343 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1344 if ($userChecksum) {
1345 //check for anonymous user.
1346 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1351 // check if user has permission, CRM-12062
1352 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1360 * Get the contact id of the logged in user
1362 function getLoggedInUserContactID() {
1363 // check if the user is logged in and has a contact ID
1364 $session = CRM_Core_Session
::singleton();
1365 return $session->get('userID');
1369 * add autoselector field -if user has permission to view contacts
1370 * If adding this to a form you also need to add to the tpl e.g
1372 * {if !empty($selectable)}
1373 * <div class="crm-summary-row">
1374 * <div class="crm-label">{$form.select_contact.label}</div>
1375 * <div class="crm-content">
1376 * {$form.select_contact.html}
1380 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1381 * @param array $field metadata of field to use as selector including
1384 * - url (for ajax lookup)
1386 * @todo add data attributes so we can deal with multiple instances on a form
1388 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1389 $autoCompleteField = array_merge(array(
1390 'name_field' => 'select_contact',
1391 'id_field' => 'select_contact_id',
1392 'field_text' => ts('Select Contact'),
1393 'show_hide' => TRUE,
1394 'show_text' => ts('to select someone already in our database.'),
1395 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
1396 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
1397 'max' => civicrm_api3('setting', 'getvalue', array(
1398 'name' => 'search_autocomplete_count',
1399 'group' => 'Search Preferences',
1401 ), $autoCompleteField);
1403 if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
1404 $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
1405 $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
1406 $this->assign('selectable', $autoCompleteField['id_field']);
1408 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
1410 'form' => array('autocompletes' => $autoCompleteField),
1411 'ids' => array('profile' => $profiles),
1417 * Add the options appropriate to cid = zero - ie. autocomplete
1419 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1420 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1421 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1422 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1424 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1425 $this->assign('nocid', TRUE);
1426 $profiles = array();
1427 if($this->_values
['custom_pre_id']) {
1428 $profiles[] = $this->_values
['custom_pre_id'];
1430 if($this->_values
['custom_post_id']) {
1431 $profiles[] = $this->_values
['custom_post_id'];
1433 if($onlinePaymentProcessorEnabled) {
1434 $profiles[] = 'billing';
1436 if(!empty($this->_values
)) {
1437 $this->addAutoSelector($profiles);
1442 * Set default values on form for given contact (or no contact defaults)
1443 * @param mixed $profile_id (can be id, or profile name)
1444 * @param integer $contactID
1446 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1448 $defaults = civicrm_api3('profile', 'getsingle', array(
1449 'profile_id' => (array) $profile_id,
1450 'contact_id' => $contactID,
1454 catch (Exception
$e) {
1455 // the try catch block gives us silent failure -not 100% sure this is a good idea
1456 // as silent failures are often worse than noisy ones