3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.4 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * This is our base form. It is part of the Form/Controller/StateMachine
30 * trifecta. Each form is associated with a specific state in the state
31 * machine. Each form can also operate in various modes
34 * @copyright CiviCRM LLC (c) 2004-2013
39 require_once 'HTML/QuickForm/Page.php';
40 class CRM_Core_Form
extends HTML_QuickForm_Page
{
43 * The state object that this form belongs to
49 * The name of this form
55 * The title of this form
58 protected $_title = NULL;
61 * The options passed into this form
64 protected $_options = NULL;
67 * The mode of operation for this form
73 * the renderer used for this form
80 * An array to hold a list of datefields on the form
81 * so that they can be converted to ISO in a consistent manner
85 * e.g on a form declare $_dateFields = array(
86 * 'receive_date' => array('default' => 'now'),
88 * then in postProcess call $this->convertDateFieldsToMySQL($formValues)
89 * to have the time field re-incorporated into the field & 'now' set if
90 * no value has been passed in
92 protected $_dateFields = array();
95 * cache the smarty template for efficiency reasons
97 * @var CRM_Core_Smarty
99 static protected $_template;
102 * What to return to the client if in ajax mode (snippet=json)
106 public $ajaxResponse = array();
109 * Url path used to reach this page
113 public $urlPath = array();
116 * Stores info about reference fields for preprocessing
117 * Public so that hooks can access it
121 public $entityReferenceFields = array();
124 * constants for attributes for various form elements
125 * attempt to standardize on the number of variations that we
126 * use of the below form elements
130 CONST ATTR_SPACING
= ' ';
133 * All checkboxes are defined with a common prefix. This allows us to
134 * have the same javascript to check / clear all the checkboxes etc
135 * If u have multiple groups of checkboxes, you will need to give them different
136 * ids to avoid potential name collision
138 * @var const string / int
140 CONST CB_PREFIX
= 'mark_x_', CB_PREFIY
= 'mark_y_', CB_PREFIZ
= 'mark_z_', CB_PREFIX_LEN
= 7;
143 * Constructor for the basic form page
145 * We should not use QuickForm directly. This class provides a lot
146 * of default convenient functions, rules and buttons
148 * @param object $state State associated with this form
149 * @param enum $action The mode the form is operating in (None/Create/View/Update/Delete)
150 * @param string $method The type of http method used (GET/POST)
151 * @param string $name The name of the form if different from class name
156 function __construct(
158 $action = CRM_Core_Action
::NONE
,
164 $this->_name
= $name;
167 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
170 $this->HTML_QuickForm_Page($this->_name
, $method);
172 $this->_state
=& $state;
174 $this->_state
->setName($this->_name
);
176 $this->_action
= (int) $action;
178 $this->registerRules();
180 // let the constructor initialize this, should happen only once
181 if (!isset(self
::$_template)) {
182 self
::$_template = CRM_Core_Smarty
::singleton();
185 $this->assign('snippet', (int) CRM_Utils_Array
::value('snippet', $_REQUEST));
188 static function generateID() {
192 * register all the standard rules that most forms potentially use
198 function registerRules() {
199 static $rules = array(
200 'title', 'longTitle', 'variable', 'qfVariable',
201 'phone', 'integer', 'query',
203 'domain', 'numberOfDigit',
204 'date', 'currentDate',
205 'asciiFile', 'htmlFile', 'utf8File',
206 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
207 'xssString', 'fileExists', 'autocomplete', 'validContact',
210 foreach ($rules as $rule) {
211 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
216 * Simple easy to use wrapper around addElement. Deal with
217 * simple validation rules
219 * @param string type of html element to be added
220 * @param string name of the html element
221 * @param string display label for the html element
222 * @param string attributes used for this element.
223 * These are not default values
224 * @param bool is this a required field
226 * @return HTML_QuickForm_Element could be an error object
230 function &add($type, $name, $label = '',
231 $attributes = '', $required = FALSE, $javascript = NULL
233 $element = $this->addElement($type, $name, $label, $attributes, $javascript);
234 if (HTML_QuickForm
::isError($element)) {
235 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
239 if ($type == 'file') {
240 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
243 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
245 if (HTML_QuickForm
::isError($error)) {
246 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
254 * This function is called before buildForm. Any pre-processing that
255 * needs to be done for buildForm should be done here
257 * This is a virtual function and should be redefined if needed
264 function preProcess() {}
267 * This function is called after the form is validated. Any
268 * processing of form state etc should be done in this function.
269 * Typically all processing associated with a form should be done
270 * here and relevant state should be stored in the session
272 * This is a virtual function and should be redefined if needed
279 function postProcess() {}
282 * This function is just a wrapper, so that we can call all the hook functions
284 function mainProcess() {
285 $this->postProcess();
286 $this->postProcessHook();
288 // Respond with JSON if in AJAX context (also support legacy value '6')
289 if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
290 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
291 $this->ajaxResponse
['action'] = $this->_action
;
292 if (isset($this->_id
) ||
isset($this->id
)) {
293 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
295 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
300 * The postProcess hook is typically called by the framework
301 * However in a few cases, the form exits or redirects early in which
302 * case it needs to call this function so other modules can do the needful
303 * Calling this function directly should be avoided if possible. In general a
304 * better way is to do setUserContext so the framework does the redirect
307 function postProcessHook() {
308 CRM_Utils_Hook
::postProcess(get_class($this), $this);
312 * This virtual function is used to build the form. It replaces the
313 * buildForm associated with QuickForm_Page. This allows us to put
314 * preProcess in front of the actual form building routine
321 function buildQuickForm() {}
324 * This virtual function is used to set the default values of
325 * various form elements
329 * @return array reference to the array of default values
332 function setDefaultValues() {}
335 * This is a virtual function that adds group and global rules to
336 * the form. Keeping it distinct from the form to keep code small
337 * and localized in the form building code
344 function addRules() {}
346 function validate() {
347 $error = parent
::validate();
349 $hookErrors = CRM_Utils_Hook
::validate(
351 $this->_submitValues
,
356 if (!is_array($hookErrors)) {
357 $hookErrors = array();
360 CRM_Utils_Hook
::validateForm(
362 $this->_submitValues
,
368 if (!empty($hookErrors)) {
369 $this->_errors +
= $hookErrors;
372 return (0 == count($this->_errors
));
376 * Core function that builds the form. We redefine this function
377 * here and expect all CRM forms to build their form in the function
381 function buildForm() {
382 $this->_formBuilt
= TRUE;
386 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
389 $this->controller
->_key
&&
390 $this->controller
->_generateQFKey
392 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
393 $this->assign('qfKey', $this->controller
->_key
);
397 // _generateQFKey suppresses the qfKey generation on form snippets that
398 // are part of other forms, hence we use that to avoid adding entryURL
399 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
400 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
403 $this->buildQuickForm();
405 $defaults = $this->setDefaultValues();
406 unset($defaults['qfKey']);
408 if (!empty($defaults)) {
409 $this->setDefaults($defaults);
412 // call the form hook
413 // also call the hook function so any modules can set thier own custom defaults
414 // the user can do both the form and set default values with this hook
415 CRM_Utils_Hook
::buildForm(get_class($this), $this);
417 $this->preprocessReferenceFields();
423 * Add default Next / Back buttons
425 * @param array array of associative arrays in the order in which the buttons should be
426 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
427 * The base form class will define a bunch of static arrays for commonly used
435 function addButtons($params) {
438 foreach ($params as $button) {
439 $js = CRM_Utils_Array
::value('js', $button);
440 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
442 $attrs = array('class' => 'form-submit default');
445 $attrs = array('class' => 'form-submit');
449 $attrs = array_merge($js, $attrs);
452 if ($button['type'] === 'reset') {
453 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
456 if (!empty($button['subName'])) {
457 $buttonName = $this->getButtonName($button['type'], $button['subName']);
460 $buttonName = $this->getButtonName($button['type']);
463 if (in_array($button['type'], array(
464 'next', 'upload')) && $button['name'] === 'Save') {
465 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
467 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
469 if (!empty($button['isDefault'])) {
470 $this->setDefaultAction($button['type']);
473 // if button type is upload, set the enctype
474 if ($button['type'] == 'upload') {
475 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
476 $this->setMaxFileSize();
479 // hack - addGroup uses an array to express variable spacing, read from the last element
480 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
482 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
486 * getter function for Name
496 * getter function for State
501 function &getState() {
502 return $this->_state
;
506 * getter function for StateType
511 function getStateType() {
512 return $this->_state
->getType();
516 * getter function for title. Should be over-ridden by derived class
521 function getTitle() {
522 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
526 * setter function for title.
528 * @param string $title the title of the form
533 function setTitle($title) {
534 $this->_title
= $title;
538 * Setter function for options
545 function setOptions($options) {
546 $this->_options
= $options;
550 * getter function for link.
556 $config = CRM_Core_Config
::singleton();
557 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
558 '_qf_' . $this->_name
. '_display=true'
563 * boolean function to determine if this is a one form page
568 function isSimpleForm() {
569 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
573 * getter function for Form Action
578 function getFormAction() {
579 return $this->_attributes
['action'];
583 * setter function for Form Action
590 function setFormAction($action) {
591 $this->_attributes
['action'] = $action;
595 * render form and return contents
600 function toSmarty() {
601 $renderer = $this->getRenderer();
602 $this->accept($renderer);
603 $content = $renderer->toArray();
604 $content['formName'] = $this->getName();
609 * getter function for renderer. If renderer is not set
610 * create one and initialize it
615 function &getRenderer() {
616 if (!isset($this->_renderer
)) {
617 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
619 return $this->_renderer
;
623 * Use the form name to create the tpl file name
628 function getTemplateFileName() {
629 $ext = CRM_Extension_System
::singleton()->getMapper();
630 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
631 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
632 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
635 $tplname = str_replace('_',
637 CRM_Utils_System
::getClassName($this)
644 * A wrapper for getTemplateFileName that includes calling the hook to
645 * prevent us from having to copy & paste the logic of calling the hook
647 function getHookedTemplateFileName() {
648 $pageTemplateFile = $this->getTemplateFileName();
649 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
650 return $pageTemplateFile;
654 * Default extra tpl file basically just replaces .tpl with .extra.tpl
655 * i.e. we dont override
660 function overrideExtraTemplateFileName() {
665 * Error reporting mechanism
667 * @param string $message Error Message
668 * @param int $code Error Code
669 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
674 function error($message, $code = NULL, $dao = NULL) {
676 $dao->query('ROLLBACK');
679 $error = CRM_Core_Error
::singleton();
681 $error->push($code, $message);
685 * Store the variable with the value in the form scope
687 * @param string name : name of the variable
688 * @param mixed value : value of the variable
695 function set($name, $value) {
696 $this->controller
->set($name, $value);
700 * Get the variable from the form scope
702 * @param string name : name of the variable
709 function get($name) {
710 return $this->controller
->get($name);
719 function getAction() {
720 return $this->_action
;
726 * @param int $action the mode we want to set the form
731 function setAction($action) {
732 $this->_action
= $action;
736 * assign value to name in template
738 * @param array|string $name name of variable
739 * @param mixed $value value of varaible
744 function assign($var, $value = NULL) {
745 self
::$_template->assign($var, $value);
749 * assign value to name in template by reference
751 * @param array|string $name name of variable
752 * @param mixed $value value of varaible
757 function assign_by_ref($var, &$value) {
758 self
::$_template->assign_by_ref($var, $value);
762 * appends values to template variables
764 * @param array|string $tpl_var the template variable name(s)
765 * @param mixed $value the value to append
768 function append($tpl_var, $value=NULL, $merge=FALSE) {
769 self
::$_template->append($tpl_var, $value, $merge);
773 * Returns an array containing template variables
775 * @param string $name
776 * @param string $type
779 function get_template_vars($name=null) {
780 return self
::$_template->get_template_vars($name);
783 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
785 $attributes = $attributes ?
$attributes : array();
786 $unselectable = !empty($attributes['unselectable']);
787 unset($attributes['unselectable']);
788 $attributes +
= array('id_suffix' => $name);
789 foreach ($values as $key => $var) {
790 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
792 $group = $this->addGroup($options, $name, $title, $separator);
794 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
797 $group->setAttribute('unselectable', TRUE);
802 function addYesNo($id, $title, $unselectable = FALSE, $required = NULL, $attributes = array()) {
803 $attributes +
= array('id_suffix' => $id);
805 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
806 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
808 $group = $this->addGroup($choice, $id, $title);
810 $group->setAttribute('unselectable', TRUE);
813 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
817 function addCheckBox($id, $title, $values, $other = NULL,
818 $attributes = NULL, $required = NULL,
819 $javascriptMethod = NULL,
820 $separator = '<br />', $flipValues = FALSE
824 if ($javascriptMethod) {
825 foreach ($values as $key => $var) {
827 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
830 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
835 foreach ($values as $key => $var) {
837 $options[] = $this->createElement('checkbox', $var, NULL, $key);
840 $options[] = $this->createElement('checkbox', $key, NULL, $var);
845 $this->addGroup($options, $id, $title, $separator);
848 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
853 ts('%1 is a required field.', array(1 => $title)),
859 function resetValues() {
860 $data = $this->controller
->container();
861 $data['values'][$this->_name
] = array();
865 * simple shell that derived classes can call to add buttons to
866 * the form with a customized title for the main Submit
868 * @param string $title title of the main button
869 * @param string $type button type for the form after processing
870 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
875 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
877 if ($backType != NULL) {
880 'name' => ts('Previous'),
883 if ($nextType != NULL) {
890 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
892 $buttons[] = $nextButton;
894 $this->addButtons($buttons);
897 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
899 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
900 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
902 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
903 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
908 * Adds a select based on field metadata
909 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
910 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
911 * @param $name - field name to go on the form
912 * @param array $props - mix of html attributes and special properties, namely
913 * - entity (api entity name, can usually be inferred automatically from the form class)
914 * - field (field name - only needed if different from name used on the form)
915 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
916 * - placeholder - set to NULL to disable
917 * @param bool $required
918 * @throws CRM_Core_Exception
919 * @return HTML_QuickForm_Element
921 function addSelect($name, $props = array(), $required = FALSE) {
922 if (!isset($props['entity'])) {
923 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
925 if (!isset($props['field'])) {
926 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
928 $info = civicrm_api3($props['entity'], 'getoptions', array(
929 'field' => $props['field'],
930 'options' => array('metadata' => array('fields'))
933 $options = $info['values'];
934 if (!array_key_exists('placeholder', $props)) {
935 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
937 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
938 $options = array('' => '') +
$options;
940 // Handle custom field
941 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
942 list(, $id) = explode('_', $name);
943 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
944 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
945 $props['data-option-group-url'] = array_key_exists('option_url', $props) ?
$props['option_url'] : 'civicrm/admin/options/' . CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', $gid);
949 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
951 $uniqueName === $props['field'] ||
952 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
953 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
958 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
959 $props['data-option-group-url'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-group-url'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
961 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
962 $props['data-api-entity'] = $props['entity'];
963 $props['data-api-field'] = $props['field'];
964 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
965 return $this->add('select', $name, $label, $options, $required, $props);
969 * Add a widget for selecting/editing/creating/copying a profile form
971 * @param string $name HTML form-element name
972 * @param string $label Printable label
973 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
974 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
975 * @param array $entities
977 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
979 // FIXME: Instead of adhoc serialization, use a single json_encode()
980 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
981 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
982 $this->add('text', $name, $label, array(
983 'class' => 'crm-profile-selector',
984 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
985 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
986 'data-entities' => json_encode($entities),
990 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
991 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
992 // 2. Based on the option, initialise proper editor
993 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
996 $editor = strtolower(CRM_Utils_Array
::value($editorID,
997 CRM_Core_OptionGroup
::values('wysiwyg_editor')
999 if (!$editor ||
$forceTextarea) {
1000 $editor = 'textarea';
1002 if ($editor == 'joomla default editor') {
1003 $editor = 'joomlaeditor';
1006 if ($editor == 'drupal default editor') {
1007 $editor = 'drupalwysiwyg';
1010 //lets add the editor as a attribute
1011 $attributes['editor'] = $editor;
1013 $this->addElement($editor, $name, $label, $attributes);
1014 $this->assign('editor', $editor);
1016 // include wysiwyg editor js files
1017 // FIXME: This code does not make any sense
1018 $includeWysiwygEditor = FALSE;
1019 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1020 if (!$includeWysiwygEditor) {
1021 $includeWysiwygEditor = TRUE;
1022 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1025 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1028 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1029 $this->addElement('select', $id, $title,
1031 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1034 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1038 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1040 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1043 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1047 public function getRootTitle() {
1051 public function getCompleteTitle() {
1052 return $this->getRootTitle() . $this->getTitle();
1055 static function &getTemplate() {
1056 return self
::$_template;
1059 function addUploadElement($elementName) {
1060 $uploadNames = $this->get('uploadNames');
1061 if (!$uploadNames) {
1062 $uploadNames = array();
1064 if (is_array($elementName)) {
1065 foreach ($elementName as $name) {
1066 if (!in_array($name, $uploadNames)) {
1067 $uploadNames[] = $name;
1072 if (!in_array($elementName, $uploadNames)) {
1073 $uploadNames[] = $elementName;
1076 $this->set('uploadNames', $uploadNames);
1078 $config = CRM_Core_Config
::singleton();
1079 if (!empty($uploadNames)) {
1080 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1084 function buttonType() {
1085 $uploadNames = $this->get('uploadNames');
1086 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1087 $this->assign('buttonType', $buttonType);
1091 function getVar($name) {
1092 return isset($this->$name) ?
$this->$name : NULL;
1095 function setVar($name, $value) {
1096 $this->$name = $value;
1100 * Function to add date
1101 * @param string $name name of the element
1102 * @param string $label label of the element
1103 * @param array $attributes key / value pair
1106 * $attributes = array ( 'addTime' => true,
1107 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1109 * @param boolean $required true if required
1112 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1113 if (!empty($attributes['formatType'])) {
1114 // get actual format
1115 $params = array('name' => $attributes['formatType']);
1118 // cache date information
1120 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1121 if (empty($dateFormat[$key])) {
1122 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1123 $dateFormat[$key] = $values;
1126 $values = $dateFormat[$key];
1129 if ($values['date_format']) {
1130 $attributes['format'] = $values['date_format'];
1133 if (!empty($values['time_format'])) {
1134 $attributes['timeFormat'] = $values['time_format'];
1136 $attributes['startOffset'] = $values['start'];
1137 $attributes['endOffset'] = $values['end'];
1140 $config = CRM_Core_Config
::singleton();
1141 if (empty($attributes['format'])) {
1142 $attributes['format'] = $config->dateInputFormat
;
1145 if (!isset($attributes['startOffset'])) {
1146 $attributes['startOffset'] = 10;
1149 if (!isset($attributes['endOffset'])) {
1150 $attributes['endOffset'] = 10;
1153 $this->add('text', $name, $label, $attributes);
1155 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1157 if (!isset($attributes['timeFormat'])) {
1158 $timeFormat = $config->timeInputFormat
;
1161 $timeFormat = $attributes['timeFormat'];
1164 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1166 $show24Hours = TRUE;
1167 if ($timeFormat == 1) {
1168 $show24Hours = FALSE;
1171 //CRM-6664 -we are having time element name
1172 //in either flat string or an array format.
1173 $elementName = $name . '_time';
1174 if (substr($name, -1) == ']') {
1175 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1178 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1183 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1184 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1185 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1191 * Function that will add date and time
1193 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1194 $addTime = array('addTime' => TRUE);
1195 if (is_array($attributes)) {
1196 $attributes = array_merge($attributes, $addTime);
1199 $attributes = $addTime;
1202 $this->addDate($name, $label, $required, $attributes);
1206 * add a currency and money element to the form
1208 function addMoney($name,
1212 $addCurrency = TRUE,
1213 $currencyName = 'currency',
1214 $defaultCurrency = NULL,
1215 $freezeCurrency = FALSE
1217 $element = $this->add('text', $name, $label, $attributes, $required);
1218 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1221 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1228 * add currency element to the form
1230 function addCurrency($name = 'currency',
1233 $defaultCurrency = NULL,
1234 $freezeCurrency = FALSE
1236 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1237 $options = array('class' => 'crm-select2 eight');
1239 $currencies = array('' => '') +
$currencies;
1240 $options['placeholder'] = ts('- none -');
1242 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1243 if ($freezeCurrency) {
1246 if (!$defaultCurrency) {
1247 $config = CRM_Core_Config
::singleton();
1248 $defaultCurrency = $config->defaultCurrency
;
1250 $this->setDefaults(array($name => $defaultCurrency));
1254 * Create a single or multiple entity ref field
1255 * @param string $name
1256 * @param string $label
1257 * @param array $props mix of html and widget properties, including:
1258 * - select - params to give to select2 widget
1259 * - entity - defaults to contact
1260 * - api - array of settings for the getlist api
1261 * - placeholder - string
1263 * - class, etc. - other html properties
1264 * @param bool $required
1265 * @return HTML_QuickForm_Element
1267 function addEntityRef($name, $label, $props = array(), $required = FALSE) {
1268 // Default properties
1269 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1270 $props['entity'] = CRM_Utils_Array
::value('entity', $props, 'contact');
1272 $props['class'] = isset($props['class']) ?
$props['class'] . ' ' : '';
1273 $props['class'] .= "crm-select2 crm-form-entityref";
1275 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
array(
1276 'minimumInputLength' => 1,
1277 'multiple' => !empty($props['multiple']),
1278 'placeholder' => CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select -') : ts('- none -')),
1279 'allowClear' => !$required,
1280 // Disabled pending https://github.com/ivaynberg/select2/pull/2092
1281 //'formatInputTooShort' => ts('Start typing a name or email address...'),
1282 //'formatNoMatches' => ts('No contacts found.'),
1285 $this->entityReferenceFields
[] = $name;
1286 $this->formatReferenceFieldAttributes($props);
1287 return $this->add('text', $name, $label, $props, $required);
1293 private function formatReferenceFieldAttributes(&$props) {
1294 $props['data-select-params'] = json_encode($props['select']);
1295 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1296 $props['data-api-entity'] = $props['entity'];
1297 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'placeholder');
1301 * Convert IDs to values and format for display
1303 private function preprocessReferenceFields() {
1304 foreach ($this->entityReferenceFields
as $name) {
1305 $field = $this->getElement($name);
1306 $val = $field->getValue();
1307 // Support array values
1308 if (is_array($val)) {
1309 $val = implode(',', $val);
1310 $field->setValue($val);
1314 $entity = $field->getAttribute('data-api-entity');
1315 $select = json_decode($field->getAttribute('data-select-params'), TRUE);
1316 // Support serialized values
1317 if (strpos($val, CRM_Core_DAO
::VALUE_SEPARATOR
) !== FALSE) {
1318 $val = str_replace(CRM_Core_DAO
::VALUE_SEPARATOR
, ',', trim($val, CRM_Core_DAO
::VALUE_SEPARATOR
));
1319 $field->setValue($val);
1321 $result = civicrm_api3($entity, 'getlist', array('params' => array('id' => $val)));
1322 if (!empty($result['values'])) {
1323 foreach($result['values'] as $row) {
1324 $data[] = array('id' => $row['id'], 'text' => $row['label']);
1327 if ($field->isFrozen()) {
1328 $field->removeAttribute('class');
1331 // Simplify array for single selects - makes client-side code simpler (but feels somehow wrong)
1332 if (empty($select['multiple'])) {
1335 $field->setAttribute('data-entity-value', json_encode($data));
1342 * Convert all date fields within the params to mysql date ready for the
1343 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1344 * and if time is defined it is incorporated
1346 * @param array $params input params from the form
1348 * @todo it would probably be better to work on $this->_params than a passed array
1349 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1352 function convertDateFieldsToMySQL(&$params){
1353 foreach ($this->_dateFields
as $fieldName => $specs){
1354 if(!empty($params[$fieldName])){
1355 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1356 CRM_Utils_Date
::processDate(
1357 $params[$fieldName],
1358 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1362 if(isset($specs['default'])){
1363 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1369 function removeFileRequiredRules($elementName) {
1370 $this->_required
= array_diff($this->_required
, array($elementName));
1371 if (isset($this->_rules
[$elementName])) {
1372 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1373 if ($ruleInfo['type'] == 'uploadedfile') {
1374 unset($this->_rules
[$elementName][$index]);
1377 if (empty($this->_rules
[$elementName])) {
1378 unset($this->_rules
[$elementName]);
1384 * Function that can be defined in Form to override or
1385 * perform specific action on cancel action
1389 function cancelAction() {}
1392 * Helper function to verify that required fields have been filled
1393 * Typically called within the scope of a FormRule function
1395 static function validateMandatoryFields($fields, $values, &$errors) {
1396 foreach ($fields as $name => $fld) {
1397 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1398 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1404 * Get contact if for a form object. Prioritise
1405 * - cid in URL if 0 (on behalf on someoneelse)
1406 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1407 * - logged in user id if it matches the one in the cid in the URL
1408 * - contact id validated from a checksum from a checksum
1409 * - cid from the url if the caller has ACL permission to view
1410 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1412 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1414 function getContactID() {
1415 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1416 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1417 $tempID = $this->_params
['select_contact_id'];
1419 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1420 // event form stores as an indexed array, contribution form not so much...
1421 $tempID = $this->_params
[0]['select_contact_id'];
1424 // force to ignore the authenticated user
1425 if ($tempID === '0' ||
$tempID === 0) {
1426 // we set the cid on the form so that this will be retained for the Confirm page
1427 // in the multi-page form & prevent us returning the $userID when this is called
1429 // we don't really need to set it when $tempID is set because the params have that stored
1430 $this->set('cid', 0);
1434 $userID = $this->getLoggedInUserContactID();
1436 if ($tempID == $userID) {
1440 //check if this is a checksum authentication
1441 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1442 if ($userChecksum) {
1443 //check for anonymous user.
1444 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1449 // check if user has permission, CRM-12062
1450 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1458 * Get the contact id of the logged in user
1460 function getLoggedInUserContactID() {
1461 // check if the user is logged in and has a contact ID
1462 $session = CRM_Core_Session
::singleton();
1463 return $session->get('userID');
1467 * add autoselector field -if user has permission to view contacts
1468 * If adding this to a form you also need to add to the tpl e.g
1470 * {if !empty($selectable)}
1471 * <div class="crm-summary-row">
1472 * <div class="crm-label">{$form.select_contact.label}</div>
1473 * <div class="crm-content">
1474 * {$form.select_contact.html}
1478 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1479 * @param array $field metadata of field to use as selector including
1482 * - url (for ajax lookup)
1484 * @todo add data attributes so we can deal with multiple instances on a form
1486 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1487 $autoCompleteField = array_merge(array(
1488 'name_field' => 'select_contact',
1489 'id_field' => 'select_contact_id',
1490 'field_text' => ts('Select Contact'),
1491 'show_hide' => TRUE,
1492 'show_text' => ts('to select someone already in our database.'),
1493 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
1494 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
1495 'max' => civicrm_api3('setting', 'getvalue', array(
1496 'name' => 'search_autocomplete_count',
1497 'group' => 'Search Preferences',
1499 ), $autoCompleteField);
1501 if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
1502 $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
1503 $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
1504 $this->assign('selectable', $autoCompleteField['id_field']);
1506 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
1508 'form' => array('autocompletes' => $autoCompleteField),
1509 'ids' => array('profile' => $profiles),
1515 * Add the options appropriate to cid = zero - ie. autocomplete
1517 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1518 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1519 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1520 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1522 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1523 $this->assign('nocid', TRUE);
1524 $profiles = array();
1525 if($this->_values
['custom_pre_id']) {
1526 $profiles[] = $this->_values
['custom_pre_id'];
1528 if($this->_values
['custom_post_id']) {
1529 $profiles[] = $this->_values
['custom_post_id'];
1531 if($onlinePaymentProcessorEnabled) {
1532 $profiles[] = 'billing';
1534 if(!empty($this->_values
)) {
1535 $this->addAutoSelector($profiles);
1540 * Set default values on form for given contact (or no contact defaults)
1541 * @param mixed $profile_id (can be id, or profile name)
1542 * @param integer $contactID
1544 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1546 $defaults = civicrm_api3('profile', 'getsingle', array(
1547 'profile_id' => (array) $profile_id,
1548 'contact_id' => $contactID,
1552 catch (Exception
$e) {
1553 // the try catch block gives us silent failure -not 100% sure this is a good idea
1554 // as silent failures are often worse than noisy ones