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
);
364 // _generateQFKey suppresses the qfKey generation on form snippets that
365 // are part of other forms, hence we use that to avoid adding entryURL
366 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
367 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
370 $this->buildQuickForm();
372 $defaults = $this->setDefaultValues();
373 unset($defaults['qfKey']);
375 if (!empty($defaults)) {
376 $this->setDefaults($defaults);
379 // call the form hook
380 // also call the hook function so any modules can set thier own custom defaults
381 // the user can do both the form and set default values with this hook
382 CRM_Utils_Hook
::buildForm(get_class($this), $this);
388 * Add default Next / Back buttons
390 * @param array array of associative arrays in the order in which the buttons should be
391 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
392 * The base form class will define a bunch of static arrays for commonly used
400 function addButtons($params) {
403 foreach ($params as $button) {
404 $js = CRM_Utils_Array
::value('js', $button);
405 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
407 $attrs = array('class' => 'form-submit default');
410 $attrs = array('class' => 'form-submit');
414 $attrs = array_merge($js, $attrs);
417 if ($button['type'] === 'reset') {
418 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
421 if (CRM_Utils_Array
::value('subName', $button)) {
422 $buttonName = $this->getButtonName($button['type'], $button['subName']);
425 $buttonName = $this->getButtonName($button['type']);
428 if (in_array($button['type'], array(
429 'next', 'upload')) && $button['name'] === 'Save') {
430 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
432 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
434 if (CRM_Utils_Array
::value('isDefault', $button)) {
435 $this->setDefaultAction($button['type']);
438 // if button type is upload, set the enctype
439 if ($button['type'] == 'upload') {
440 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
441 $this->setMaxFileSize();
444 // hack - addGroup uses an array to express variable spacing, read from the last element
445 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
447 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
451 * getter function for Name
461 * getter function for State
466 function &getState() {
467 return $this->_state
;
471 * getter function for StateType
476 function getStateType() {
477 return $this->_state
->getType();
481 * getter function for title. Should be over-ridden by derived class
486 function getTitle() {
487 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
491 * setter function for title.
493 * @param string $title the title of the form
498 function setTitle($title) {
499 $this->_title
= $title;
503 * Setter function for options
510 function setOptions($options) {
511 $this->_options
= $options;
515 * getter function for link.
521 $config = CRM_Core_Config
::singleton();
522 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
523 '_qf_' . $this->_name
. '_display=true'
528 * boolean function to determine if this is a one form page
533 function isSimpleForm() {
534 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
538 * getter function for Form Action
543 function getFormAction() {
544 return $this->_attributes
['action'];
548 * setter function for Form Action
555 function setFormAction($action) {
556 $this->_attributes
['action'] = $action;
560 * render form and return contents
565 function toSmarty() {
566 $renderer = $this->getRenderer();
567 $this->accept($renderer);
568 $content = $renderer->toArray();
569 $content['formName'] = $this->getName();
574 * getter function for renderer. If renderer is not set
575 * create one and initialize it
580 function &getRenderer() {
581 if (!isset($this->_renderer
)) {
582 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
584 return $this->_renderer
;
588 * Use the form name to create the tpl file name
593 function getTemplateFileName() {
594 $ext = CRM_Extension_System
::singleton()->getMapper();
595 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
596 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
597 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
600 $tplname = str_replace('_',
602 CRM_Utils_System
::getClassName($this)
609 * A wrapper for getTemplateFileName that includes calling the hook to
610 * prevent us from having to copy & paste the logic of calling the hook
612 function getHookedTemplateFileName() {
613 $pageTemplateFile = $this->getTemplateFileName();
614 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
615 return $pageTemplateFile;
619 * Default extra tpl file basically just replaces .tpl with .extra.tpl
620 * i.e. we dont override
625 function overrideExtraTemplateFileName() {
630 * Error reporting mechanism
632 * @param string $message Error Message
633 * @param int $code Error Code
634 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
639 function error($message, $code = NULL, $dao = NULL) {
641 $dao->query('ROLLBACK');
644 $error = CRM_Core_Error
::singleton();
646 $error->push($code, $message);
650 * Store the variable with the value in the form scope
652 * @param string name : name of the variable
653 * @param mixed value : value of the variable
660 function set($name, $value) {
661 $this->controller
->set($name, $value);
665 * Get the variable from the form scope
667 * @param string name : name of the variable
674 function get($name) {
675 return $this->controller
->get($name);
684 function getAction() {
685 return $this->_action
;
691 * @param int $action the mode we want to set the form
696 function setAction($action) {
697 $this->_action
= $action;
701 * assign value to name in template
703 * @param array|string $name name of variable
704 * @param mixed $value value of varaible
709 function assign($var, $value = NULL) {
710 self
::$_template->assign($var, $value);
714 * assign value to name in template by reference
716 * @param array|string $name name of variable
717 * @param mixed $value value of varaible
722 function assign_by_ref($var, &$value) {
723 self
::$_template->assign_by_ref($var, $value);
727 * appends values to template variables
729 * @param array|string $tpl_var the template variable name(s)
730 * @param mixed $value the value to append
733 function append($tpl_var, $value=NULL, $merge=FALSE) {
734 self
::$_template->append($tpl_var, $value, $merge);
738 * Returns an array containing template variables
740 * @param string $name
741 * @param string $type
744 function get_template_vars($name=null) {
745 return self
::$_template->get_template_vars($name);
748 function &addRadio($name, $title, &$values, $attributes = NULL, $separator = NULL, $required = FALSE) {
750 if (empty($attributes)) {
751 $attributes = array('id_suffix' => $name);
754 $attributes = array_merge($attributes, array('id_suffix' => $name));
756 foreach ($values as $key => $var) {
757 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
759 $group = $this->addGroup($options, $name, $title, $separator);
761 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
766 function addYesNo($id, $title, $dontKnow = NULL, $required = NULL, $attribute = NULL) {
767 if (empty($attribute)) {
768 $attribute = array('id_suffix' => $id);
771 $attribute = array_merge($attribute, array('id_suffix' => $id));
774 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attribute);
775 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attribute);
777 $choice[] = $this->createElement('radio', NULL, '22', ts("Don't Know"), '2', $attribute);
779 $this->addGroup($choice, $id, $title);
782 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
786 function addCheckBox($id, $title, $values, $other = NULL,
787 $attributes = NULL, $required = NULL,
788 $javascriptMethod = NULL,
789 $separator = '<br />', $flipValues = FALSE
793 if ($javascriptMethod) {
794 foreach ($values as $key => $var) {
796 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
799 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
804 foreach ($values as $key => $var) {
806 $options[] = $this->createElement('checkbox', $var, NULL, $key);
809 $options[] = $this->createElement('checkbox', $key, NULL, $var);
814 $this->addGroup($options, $id, $title, $separator);
817 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
822 ts('%1 is a required field.', array(1 => $title)),
828 function resetValues() {
829 $data = $this->controller
->container();
830 $data['values'][$this->_name
] = array();
834 * simple shell that derived classes can call to add buttons to
835 * the form with a customized title for the main Submit
837 * @param string $title title of the main button
838 * @param string $type button type for the form after processing
839 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
844 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
846 if ($backType != NULL) {
849 'name' => ts('Previous'),
852 if ($nextType != NULL) {
859 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
861 $buttons[] = $nextButton;
863 $this->addButtons($buttons);
866 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
868 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
869 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
871 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
872 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
876 function addSelect($name, $label, $prefix = NULL, $required = NULL, $extra = NULL, $select = '- select -') {
878 $this->addElement('select', $name . '_id' . $prefix, $label,
880 '' => $select) + CRM_Core_OptionGroup
::values($name), $extra
883 $this->addRule($name . '_id' . $prefix, ts('Please select %1', array(1 => $label)), 'required');
887 $this->addElement('select', $name . '_id', $label,
889 '' => $select) + CRM_Core_OptionGroup
::values($name), $extra
892 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
898 * Add a widget for selecting/editing/creating/copying a profile form
900 * @param string $name HTML form-element name
901 * @param string $label Printable label
902 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
903 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
904 * @param array $entities
906 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
908 // FIXME: Instead of adhoc serialization, use a single json_encode()
909 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
910 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
911 $this->add('text', $name, $label, array(
912 'class' => 'crm-profile-selector',
913 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
914 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
915 'data-entities' => json_encode($entities),
919 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
920 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
921 // 2. Based on the option, initialise proper editor
922 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
925 $editor = strtolower(CRM_Utils_Array
::value($editorID,
926 CRM_Core_OptionGroup
::values('wysiwyg_editor')
928 if (!$editor ||
$forceTextarea) {
929 $editor = 'textarea';
931 if ($editor == 'joomla default editor') {
932 $editor = 'joomlaeditor';
935 if ($editor == 'drupal default editor') {
936 $editor = 'drupalwysiwyg';
939 //lets add the editor as a attribute
940 $attributes['editor'] = $editor;
942 $this->addElement($editor, $name, $label, $attributes);
943 $this->assign('editor', $editor);
945 // include wysiwyg editor js files
946 $includeWysiwygEditor = FALSE;
947 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
948 if (!$includeWysiwygEditor) {
949 $includeWysiwygEditor = TRUE;
950 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
953 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
956 function addCountry($id, $title, $required = NULL, $extra = NULL) {
957 $this->addElement('select', $id, $title,
959 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
962 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
966 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
968 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
971 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
975 function buildAddressBlock($locationId, $title, $phone,
976 $alternatePhone = NULL, $addressRequired = NULL,
977 $phoneRequired = NULL, $altPhoneRequired = NULL,
980 if (!$locationName) {
981 $locationName = "location";
984 $config = CRM_Core_Config
::singleton();
985 $attributes = CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Address');
987 $location[$locationId]['address']['street_address'] = $this->addElement('text', "{$locationName}[$locationId][address][street_address]", $title,
988 $attributes['street_address']
990 if ($addressRequired) {
991 $this->addRule("{$locationName}[$locationId][address][street_address]", ts("Please enter the Street Address for %1.", array(1 => $title)), 'required');
994 $location[$locationId]['address']['supplemental_address_1'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_1]", ts('Supplemental Address 1'),
995 $attributes['supplemental_address_1']
997 $location[$locationId]['address']['supplemental_address_2'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_2]", ts('Supplemental Address 2'),
998 $attributes['supplemental_address_2']
1001 $location[$locationId]['address']['city'] = $this->addElement('text', "{$locationName}[$locationId][address][city]", ts('City'),
1004 if ($addressRequired) {
1005 $this->addRule("{$locationName}[$locationId][address][city]", ts("Please enter the City for %1.", array(1 => $title)), 'required');
1008 $location[$locationId]['address']['postal_code'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code]", ts('Zip / Postal Code'),
1009 $attributes['postal_code']
1011 if ($addressRequired) {
1012 $this->addRule("{$locationName}[$locationId][address][postal_code]", ts("Please enter the Zip/Postal Code for %1.", array(1 => $title)), 'required');
1015 $location[$locationId]['address']['postal_code_suffix'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code_suffix]", ts('Add-on Code'),
1016 array('size' => 4, 'maxlength' => 12)
1018 $this->addRule("{$locationName}[$locationId][address][postal_code_suffix]", ts('Zip-Plus not valid.'), 'positiveInteger');
1020 if ($config->includeCounty
) {
1021 $location[$locationId]['address']['county_id'] = $this->addElement('select', "{$locationName}[$locationId][address][county_id]", ts('County'),
1022 array('' => ts('- select -')) + CRM_Core_PseudoConstant
::county()
1026 $location[$locationId]['address']['state_province_id'] = $this->addElement('select', "{$locationName}[$locationId][address][state_province_id]", ts('State / Province'),
1027 array('' => ts('- select -')) + CRM_Core_PseudoConstant
::stateProvince()
1030 $location[$locationId]['address']['country_id'] = $this->addElement('select', "{$locationName}[$locationId][address][country_id]", ts('Country'),
1031 array('' => ts('- select -')) + CRM_Core_PseudoConstant
::country()
1033 if ($addressRequired) {
1034 $this->addRule("{$locationName}[$locationId][address][country_id]", ts("Please select the Country for %1.", array(1 => $title)), 'required');
1039 $location[$locationId]['phone'][1]['phone'] = $this->addElement('text',
1040 "{$locationName}[$locationId][phone][1][phone]",
1042 CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Phone',
1046 if ($phoneRequired) {
1047 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a value for %1', array(1 => $phone)), 'required');
1049 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a valid number for %1', array(1 => $phone)), 'phone');
1052 if ($alternatePhone) {
1053 $location[$locationId]['phone'][2]['phone'] = $this->addElement('text',
1054 "{$locationName}[$locationId][phone][2][phone]",
1056 CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Phone',
1060 if ($alternatePhoneRequired) {
1061 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a value for %1', array(1 => $alternatePhone)), 'required');
1063 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a valid number for %1', array(1 => $alternatePhone)), 'phone');
1067 public function getRootTitle() {
1071 public function getCompleteTitle() {
1072 return $this->getRootTitle() . $this->getTitle();
1075 static function &getTemplate() {
1076 return self
::$_template;
1079 function addUploadElement($elementName) {
1080 $uploadNames = $this->get('uploadNames');
1081 if (!$uploadNames) {
1082 $uploadNames = array();
1084 if (is_array($elementName)) {
1085 foreach ($elementName as $name) {
1086 if (!in_array($name, $uploadNames)) {
1087 $uploadNames[] = $name;
1092 if (!in_array($elementName, $uploadNames)) {
1093 $uploadNames[] = $elementName;
1096 $this->set('uploadNames', $uploadNames);
1098 $config = CRM_Core_Config
::singleton();
1099 if (!empty($uploadNames)) {
1100 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1104 function buttonType() {
1105 $uploadNames = $this->get('uploadNames');
1106 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1107 $this->assign('buttonType', $buttonType);
1111 function getVar($name) {
1112 return isset($this->$name) ?
$this->$name : NULL;
1115 function setVar($name, $value) {
1116 $this->$name = $value;
1120 * Function to add date
1121 * @param string $name name of the element
1122 * @param string $label label of the element
1123 * @param array $attributes key / value pair
1126 * $attributes = array ( 'addTime' => true,
1127 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1129 * @param boolean $required true if required
1132 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1133 if (CRM_Utils_Array
::value('formatType', $attributes)) {
1134 // get actual format
1135 $params = array('name' => $attributes['formatType']);
1138 // cache date information
1140 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1141 if (!CRM_Utils_Array
::value($key, $dateFormat)) {
1142 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1143 $dateFormat[$key] = $values;
1146 $values = $dateFormat[$key];
1149 if ($values['date_format']) {
1150 $attributes['format'] = $values['date_format'];
1153 if (CRM_Utils_Array
::value('time_format', $values)) {
1154 $attributes['timeFormat'] = $values['time_format'];
1156 $attributes['startOffset'] = $values['start'];
1157 $attributes['endOffset'] = $values['end'];
1160 $config = CRM_Core_Config
::singleton();
1161 if (!CRM_Utils_Array
::value('format', $attributes)) {
1162 $attributes['format'] = $config->dateInputFormat
;
1165 if (!isset($attributes['startOffset'])) {
1166 $attributes['startOffset'] = 10;
1169 if (!isset($attributes['endOffset'])) {
1170 $attributes['endOffset'] = 10;
1173 $this->add('text', $name, $label, $attributes);
1175 if (CRM_Utils_Array
::value('addTime', $attributes) ||
1176 CRM_Utils_Array
::value('timeFormat', $attributes)
1179 if (!isset($attributes['timeFormat'])) {
1180 $timeFormat = $config->timeInputFormat
;
1183 $timeFormat = $attributes['timeFormat'];
1186 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1188 $show24Hours = TRUE;
1189 if ($timeFormat == 1) {
1190 $show24Hours = FALSE;
1193 //CRM-6664 -we are having time element name
1194 //in either flat string or an array format.
1195 $elementName = $name . '_time';
1196 if (substr($name, -1) == ']') {
1197 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1200 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1205 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1206 if (CRM_Utils_Array
::value('addTime', $attributes) && CRM_Utils_Array
::value('addTimeRequired', $attributes)) {
1207 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1213 * Function that will add date and time
1215 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1216 $addTime = array('addTime' => TRUE);
1217 if (is_array($attributes)) {
1218 $attributes = array_merge($attributes, $addTime);
1221 $attributes = $addTime;
1224 $this->addDate($name, $label, $required, $attributes);
1228 * add a currency and money element to the form
1230 function addMoney($name,
1234 $addCurrency = TRUE,
1235 $currencyName = 'currency',
1236 $defaultCurrency = NULL,
1237 $freezeCurrency = FALSE
1239 $element = $this->add('text', $name, $label, $attributes, $required);
1240 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1243 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1250 * add currency element to the form
1252 function addCurrency($name = 'currency',
1255 $defaultCurrency = NULL,
1256 $freezeCurrency = FALSE
1258 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1260 $currencies = array(
1261 '' => ts('- select -')) +
$currencies;
1263 $ele = $this->add('select', $name, $label, $currencies, $required);
1264 if ($freezeCurrency) {
1267 if (!$defaultCurrency) {
1268 $config = CRM_Core_Config
::singleton();
1269 $defaultCurrency = $config->defaultCurrency
;
1271 $this->setDefaults(array($name => $defaultCurrency));
1275 * Convert all date fields within the params to mysql date ready for the
1276 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1277 * and if time is defined it is incorporated
1279 * @param array $params input params from the form
1281 * @todo it would probably be better to work on $this->_params than a passed array
1282 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1285 function convertDateFieldsToMySQL(&$params){
1286 foreach ($this->_dateFields
as $fieldName => $specs){
1287 if(!empty($params[$fieldName])){
1288 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1289 CRM_Utils_Date
::processDate(
1290 $params[$fieldName],
1291 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1295 if(isset($specs['default'])){
1296 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1302 function removeFileRequiredRules($elementName) {
1303 $this->_required
= array_diff($this->_required
, array($elementName));
1304 if (isset($this->_rules
[$elementName])) {
1305 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1306 if ($ruleInfo['type'] == 'uploadedfile') {
1307 unset($this->_rules
[$elementName][$index]);
1310 if (empty($this->_rules
[$elementName])) {
1311 unset($this->_rules
[$elementName]);
1317 * Function that can be defined in Form to override or
1318 * perform specific action on cancel action
1322 function cancelAction() {}
1325 * Helper function to verify that required fields have been filled
1326 * Typically called within the scope of a FormRule function
1328 static function validateMandatoryFields($fields, $values, &$errors) {
1329 foreach ($fields as $name => $fld) {
1330 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1331 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1337 * Get contact if for a form object. Prioritise
1338 * - cid in URL if 0 (on behalf on someoneelse)
1339 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1340 * - logged in user id if it matches the one in the cid in the URL
1341 * - contact id validated from a checksum from a checksum
1342 * - cid from the url if the caller has ACL permission to view
1343 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1345 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1347 function getContactID() {
1348 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1349 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1350 $tempID = $this->_params
['select_contact_id'];
1352 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1353 // event form stores as an indexed array, contribution form not so much...
1354 $tempID = $this->_params
[0]['select_contact_id'];
1357 // force to ignore the authenticated user
1358 if ($tempID === '0' ||
$tempID === 0) {
1359 // we set the cid on the form so that this will be retained for the Confirm page
1360 // in the multi-page form & prevent us returning the $userID when this is called
1362 // we don't really need to set it when $tempID is set because the params have that stored
1363 $this->set('cid', 0);
1367 $userID = $this->getLoggedInUserContactID();
1369 if ($tempID == $userID) {
1373 //check if this is a checksum authentication
1374 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1375 if ($userChecksum) {
1376 //check for anonymous user.
1377 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1382 // check if user has permission, CRM-12062
1383 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1391 * Get the contact id of the logged in user
1393 function getLoggedInUserContactID() {
1394 // check if the user is logged in and has a contact ID
1395 $session = CRM_Core_Session
::singleton();
1396 return $session->get('userID');
1400 * add autoselector field -if user has permission to view contacts
1401 * If adding this to a form you also need to add to the tpl e.g
1403 * {if !empty($selectable)}
1404 * <div class="crm-summary-row">
1405 * <div class="crm-label">{$form.select_contact.label}</div>
1406 * <div class="crm-content">
1407 * {$form.select_contact.html}
1411 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1412 * @param array $field metadata of field to use as selector including
1415 * - url (for ajax lookup)
1417 * @todo add data attributes so we can deal with multiple instances on a form
1419 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1420 $autoCompleteField = array_merge(array(
1421 'name_field' => 'select_contact',
1422 'id_field' => 'select_contact_id',
1423 'field_text' => ts('Select Contact'),
1424 'show_hide' => TRUE,
1425 'show_text' => ts('to select someone already in our database.'),
1426 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
1427 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
1428 'max' => civicrm_api3('setting', 'getvalue', array(
1429 'name' => 'search_autocomplete_count',
1430 'group' => 'Search Preferences',
1432 ), $autoCompleteField);
1434 if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
1435 $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
1436 $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
1437 $this->assign('selectable', $autoCompleteField['id_field']);
1439 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
1441 'form' => array('autocompletes' => $autoCompleteField),
1442 'ids' => array('profile' => $profiles),
1448 * Add the options appropriate to cid = zero - ie. autocomplete
1450 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1451 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1452 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1453 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1455 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1456 $this->assign('nocid', TRUE);
1457 $profiles = array();
1458 if($this->_values
['custom_pre_id']) {
1459 $profiles[] = $this->_values
['custom_pre_id'];
1461 if($this->_values
['custom_post_id']) {
1462 $profiles[] = $this->_values
['custom_post_id'];
1464 if($onlinePaymentProcessorEnabled) {
1465 $profiles[] = 'billing';
1467 if(!empty($this->_values
)) {
1468 $this->addAutoSelector($profiles);
1473 * Set default values on form for given contact (or no contact defaults)
1474 * @param mixed $profile_id (can be id, or profile name)
1475 * @param integer $contactID
1477 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1479 $defaults = civicrm_api3('profile', 'getsingle', array(
1480 'profile_id' => (array) $profile_id,
1481 'contact_id' => $contactID,
1485 catch (Exception
$e) {
1486 // the try catch block gives us silent failure -not 100% sure this is a good idea
1487 // as silent failures are often worse than noisy ones