3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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-2014
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 * Indicate if this form should warn users of unsaved changes
104 protected $unsavedChangesWarn;
107 * What to return to the client if in ajax mode (snippet=json)
111 public $ajaxResponse = array();
114 * Url path used to reach this page
118 public $urlPath = array();
121 * constants for attributes for various form elements
122 * attempt to standardize on the number of variations that we
123 * use of the below form elements
127 CONST ATTR_SPACING
= ' ';
130 * All checkboxes are defined with a common prefix. This allows us to
131 * have the same javascript to check / clear all the checkboxes etc
132 * If u have multiple groups of checkboxes, you will need to give them different
133 * ids to avoid potential name collision
135 * @var const string / int
137 CONST CB_PREFIX
= 'mark_x_', CB_PREFIY
= 'mark_y_', CB_PREFIZ
= 'mark_z_', CB_PREFIX_LEN
= 7;
140 * Constructor for the basic form page
142 * We should not use QuickForm directly. This class provides a lot
143 * of default convenient functions, rules and buttons
145 * @param object $state State associated with this form
146 * @param \const|\enum $action The mode the form is operating in (None/Create/View/Update/Delete)
147 * @param string $method The type of http method used (GET/POST)
148 * @param string $name The name of the form if different from class name
150 * @return \CRM_Core_Form
153 function __construct(
155 $action = CRM_Core_Action
::NONE
,
161 $this->_name
= $name;
164 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
167 $this->HTML_QuickForm_Page($this->_name
, $method);
169 $this->_state
=& $state;
171 $this->_state
->setName($this->_name
);
173 $this->_action
= (int) $action;
175 $this->registerRules();
177 // let the constructor initialize this, should happen only once
178 if (!isset(self
::$_template)) {
179 self
::$_template = CRM_Core_Smarty
::singleton();
182 $this->assign('snippet', CRM_Utils_Array
::value('snippet', $_GET));
185 static function generateID() {
189 * register all the standard rules that most forms potentially use
195 function registerRules() {
196 static $rules = array(
197 'title', 'longTitle', 'variable', 'qfVariable',
198 'phone', 'integer', 'query',
200 'domain', 'numberOfDigit',
201 'date', 'currentDate',
202 'asciiFile', 'htmlFile', 'utf8File',
203 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
204 'xssString', 'fileExists', 'autocomplete', 'validContact',
207 foreach ($rules as $rule) {
208 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
213 * Simple easy to use wrapper around addElement. Deal with
214 * simple validation rules
218 * @param string $label
219 * @param string $attributes
220 * @param bool $required
223 * @internal param \type $string of html element to be added
224 * @internal param \name $string of the html element
225 * @internal param \display $string label for the html element
226 * @internal param \attributes $string used for this element.
227 * These are not default values
228 * @internal param \is $bool this a required field
230 * @return HTML_QuickForm_Element could be an error object
233 function &add($type, $name, $label = '',
234 $attributes = '', $required = FALSE, $extra = NULL
236 // Normalize this property
237 if ($type == 'select' && is_array($extra) && !empty($extra['multiple'])) {
238 $extra['multiple'] = 'multiple';
240 $element = $this->addElement($type, $name, $label, $attributes, $extra);
241 if (HTML_QuickForm
::isError($element)) {
242 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
246 if ($type == 'file') {
247 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
250 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
252 if (HTML_QuickForm
::isError($error)) {
253 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
261 * This function is called before buildForm. Any pre-processing that
262 * needs to be done for buildForm should be done here
264 * This is a virtual function and should be redefined if needed
271 function preProcess() {}
274 * This function is called after the form is validated. Any
275 * processing of form state etc should be done in this function.
276 * Typically all processing associated with a form should be done
277 * here and relevant state should be stored in the session
279 * This is a virtual function and should be redefined if needed
286 function postProcess() {}
289 * This function is just a wrapper, so that we can call all the hook functions
291 function mainProcess() {
292 $this->postProcess();
293 $this->postProcessHook();
295 // Respond with JSON if in AJAX context (also support legacy value '6')
296 if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
297 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
298 $this->ajaxResponse
['action'] = $this->_action
;
299 if (isset($this->_id
) ||
isset($this->id
)) {
300 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
302 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
307 * The postProcess hook is typically called by the framework
308 * However in a few cases, the form exits or redirects early in which
309 * case it needs to call this function so other modules can do the needful
310 * Calling this function directly should be avoided if possible. In general a
311 * better way is to do setUserContext so the framework does the redirect
314 function postProcessHook() {
315 CRM_Utils_Hook
::postProcess(get_class($this), $this);
319 * This virtual function is used to build the form. It replaces the
320 * buildForm associated with QuickForm_Page. This allows us to put
321 * preProcess in front of the actual form building routine
328 function buildQuickForm() {}
331 * This virtual function is used to set the default values of
332 * various form elements
336 * @return array reference to the array of default values
339 function setDefaultValues() {}
342 * This is a virtual function that adds group and global rules to
343 * the form. Keeping it distinct from the form to keep code small
344 * and localized in the form building code
351 function addRules() {}
353 function validate() {
354 $error = parent
::validate();
356 $hookErrors = CRM_Utils_Hook
::validate(
358 $this->_submitValues
,
363 if (!is_array($hookErrors)) {
364 $hookErrors = array();
367 CRM_Utils_Hook
::validateForm(
369 $this->_submitValues
,
375 if (!empty($hookErrors)) {
376 $this->_errors +
= $hookErrors;
379 return (0 == count($this->_errors
));
383 * Core function that builds the form. We redefine this function
384 * here and expect all CRM forms to build their form in the function
388 function buildForm() {
389 $this->_formBuilt
= TRUE;
393 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
396 $this->controller
->_key
&&
397 $this->controller
->_generateQFKey
399 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
400 $this->assign('qfKey', $this->controller
->_key
);
404 // _generateQFKey suppresses the qfKey generation on form snippets that
405 // are part of other forms, hence we use that to avoid adding entryURL
406 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
407 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
410 $this->buildQuickForm();
412 $defaults = $this->setDefaultValues();
413 unset($defaults['qfKey']);
415 if (!empty($defaults)) {
416 $this->setDefaults($defaults);
419 // call the form hook
420 // also call the hook function so any modules can set thier own custom defaults
421 // the user can do both the form and set default values with this hook
422 CRM_Utils_Hook
::buildForm(get_class($this), $this);
426 //Set html data-attribute to enable warning user of unsaved changes
427 if ($this->unsavedChangesWarn
=== true
428 ||
(!isset($this->unsavedChangesWarn
)
429 && ($this->_action
& CRM_Core_Action
::ADD ||
$this->_action
& CRM_Core_Action
::UPDATE
)
432 $this->setAttribute('data-warn-changes', 'true');
437 * Add default Next / Back buttons
439 * @param array array of associative arrays in the order in which the buttons should be
440 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
441 * The base form class will define a bunch of static arrays for commonly used
449 function addButtons($params) {
452 foreach ($params as $button) {
453 $js = CRM_Utils_Array
::value('js', $button);
454 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
456 $attrs = array('class' => 'form-submit default');
459 $attrs = array('class' => 'form-submit');
463 $attrs = array_merge($js, $attrs);
466 if ($button['type'] === 'cancel') {
467 $attrs['class'] .= ' cancel';
470 if ($button['type'] === 'reset') {
471 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
474 if (!empty($button['subName'])) {
475 $buttonName = $this->getButtonName($button['type'], $button['subName']);
478 $buttonName = $this->getButtonName($button['type']);
481 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
482 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
484 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
486 if (!empty($button['isDefault'])) {
487 $this->setDefaultAction($button['type']);
490 // if button type is upload, set the enctype
491 if ($button['type'] == 'upload') {
492 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
493 $this->setMaxFileSize();
496 // hack - addGroup uses an array to express variable spacing, read from the last element
497 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
499 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
503 * getter function for Name
513 * getter function for State
518 function &getState() {
519 return $this->_state
;
523 * getter function for StateType
528 function getStateType() {
529 return $this->_state
->getType();
533 * getter function for title. Should be over-ridden by derived class
538 function getTitle() {
539 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
543 * setter function for title.
545 * @param string $title the title of the form
550 function setTitle($title) {
551 $this->_title
= $title;
555 * Setter function for options
562 function setOptions($options) {
563 $this->_options
= $options;
567 * getter function for link.
573 $config = CRM_Core_Config
::singleton();
574 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
575 '_qf_' . $this->_name
. '_display=true'
580 * boolean function to determine if this is a one form page
585 function isSimpleForm() {
586 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
590 * getter function for Form Action
595 function getFormAction() {
596 return $this->_attributes
['action'];
600 * setter function for Form Action
607 function setFormAction($action) {
608 $this->_attributes
['action'] = $action;
612 * render form and return contents
617 function toSmarty() {
618 $renderer = $this->getRenderer();
619 $this->accept($renderer);
620 $content = $renderer->toArray();
621 $content['formName'] = $this->getName();
626 * getter function for renderer. If renderer is not set
627 * create one and initialize it
632 function &getRenderer() {
633 if (!isset($this->_renderer
)) {
634 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
636 return $this->_renderer
;
640 * Use the form name to create the tpl file name
645 function getTemplateFileName() {
646 $ext = CRM_Extension_System
::singleton()->getMapper();
647 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
648 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
649 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
652 $tplname = str_replace('_',
654 CRM_Utils_System
::getClassName($this)
661 * A wrapper for getTemplateFileName that includes calling the hook to
662 * prevent us from having to copy & paste the logic of calling the hook
664 function getHookedTemplateFileName() {
665 $pageTemplateFile = $this->getTemplateFileName();
666 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
667 return $pageTemplateFile;
671 * Default extra tpl file basically just replaces .tpl with .extra.tpl
672 * i.e. we dont override
677 function overrideExtraTemplateFileName() {
682 * Error reporting mechanism
684 * @param string $message Error Message
685 * @param int $code Error Code
686 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
691 function error($message, $code = NULL, $dao = NULL) {
693 $dao->query('ROLLBACK');
696 $error = CRM_Core_Error
::singleton();
698 $error->push($code, $message);
702 * Store the variable with the value in the form scope
704 * @param string name : name of the variable
705 * @param mixed value : value of the variable
712 function set($name, $value) {
713 $this->controller
->set($name, $value);
717 * Get the variable from the form scope
719 * @param string name : name of the variable
726 function get($name) {
727 return $this->controller
->get($name);
736 function getAction() {
737 return $this->_action
;
743 * @param int $action the mode we want to set the form
748 function setAction($action) {
749 $this->_action
= $action;
753 * assign value to name in template
755 * @param array|string $name name of variable
756 * @param mixed $value value of varaible
761 function assign($var, $value = NULL) {
762 self
::$_template->assign($var, $value);
766 * assign value to name in template by reference
768 * @param array|string $name name of variable
769 * @param mixed $value value of varaible
774 function assign_by_ref($var, &$value) {
775 self
::$_template->assign_by_ref($var, $value);
779 * appends values to template variables
781 * @param array|string $tpl_var the template variable name(s)
782 * @param mixed $value the value to append
785 function append($tpl_var, $value=NULL, $merge=FALSE) {
786 self
::$_template->append($tpl_var, $value, $merge);
790 * Returns an array containing template variables
792 * @param string $name
793 * @param string $type
796 function get_template_vars($name=null) {
797 return self
::$_template->get_template_vars($name);
800 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
802 $attributes = $attributes ?
$attributes : array();
803 $allowClear = !empty($attributes['allowClear']);
804 unset($attributes['allowClear']);
805 $attributes +
= array('id_suffix' => $name);
806 foreach ($values as $key => $var) {
807 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
809 $group = $this->addGroup($options, $name, $title, $separator);
811 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
814 $group->setAttribute('allowClear', TRUE);
819 function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
820 $attributes +
= array('id_suffix' => $id);
822 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
823 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
825 $group = $this->addGroup($choice, $id, $title);
827 $group->setAttribute('allowClear', TRUE);
830 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
834 function addCheckBox($id, $title, $values, $other = NULL,
835 $attributes = NULL, $required = NULL,
836 $javascriptMethod = NULL,
837 $separator = '<br />', $flipValues = FALSE
841 if ($javascriptMethod) {
842 foreach ($values as $key => $var) {
844 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
847 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
852 foreach ($values as $key => $var) {
854 $options[] = $this->createElement('checkbox', $var, NULL, $key);
857 $options[] = $this->createElement('checkbox', $key, NULL, $var);
862 $this->addGroup($options, $id, $title, $separator);
865 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
870 ts('%1 is a required field.', array(1 => $title)),
876 function resetValues() {
877 $data = $this->controller
->container();
878 $data['values'][$this->_name
] = array();
882 * simple shell that derived classes can call to add buttons to
883 * the form with a customized title for the main Submit
885 * @param string $title title of the main button
886 * @param string $type button type for the form after processing
887 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
892 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
894 if ($backType != NULL) {
897 'name' => ts('Previous'),
900 if ($nextType != NULL) {
907 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
909 $buttons[] = $nextButton;
911 $this->addButtons($buttons);
914 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
916 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
917 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
919 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
920 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
925 * Adds a select based on field metadata
926 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
927 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
928 * @param $name - field name to go on the form
929 * @param array $props - mix of html attributes and special properties, namely
930 * - entity (api entity name, can usually be inferred automatically from the form class)
931 * - field (field name - only needed if different from name used on the form)
932 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
933 * - placeholder - set to NULL to disable
935 * @param bool $required
936 * @throws CRM_Core_Exception
937 * @return HTML_QuickForm_Element
939 function addSelect($name, $props = array(), $required = FALSE) {
940 if (!isset($props['entity'])) {
941 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
943 if (!isset($props['field'])) {
944 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
946 $info = civicrm_api3($props['entity'], 'getoptions', array(
947 'field' => $props['field'],
948 'options' => array('metadata' => array('fields'))
951 $options = $info['values'];
952 if (!array_key_exists('placeholder', $props)) {
953 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
955 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
956 $options = array('' => '') +
$options;
958 // Handle custom field
959 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
960 list(, $id) = explode('_', $name);
961 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
962 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
963 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : 'civicrm/admin/options/' . CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', $gid);
967 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
969 $uniqueName === $props['field'] ||
970 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
971 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
976 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
977 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
979 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
980 $props['data-api-entity'] = $props['entity'];
981 $props['data-api-field'] = $props['field'];
982 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
983 return $this->add('select', $name, $label, $options, $required, $props);
987 * Add a widget for selecting/editing/creating/copying a profile form
989 * @param string $name HTML form-element name
990 * @param string $label Printable label
991 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
992 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
993 * @param array $entities
995 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
997 // FIXME: Instead of adhoc serialization, use a single json_encode()
998 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
999 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1000 $this->add('text', $name, $label, array(
1001 'class' => 'crm-profile-selector',
1002 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1003 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1004 'data-entities' => json_encode($entities),
1008 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1009 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1010 // 2. Based on the option, initialise proper editor
1011 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1014 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1015 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1017 if (!$editor ||
$forceTextarea) {
1018 $editor = 'textarea';
1020 if ($editor == 'joomla default editor') {
1021 $editor = 'joomlaeditor';
1024 if ($editor == 'drupal default editor') {
1025 $editor = 'drupalwysiwyg';
1028 //lets add the editor as a attribute
1029 $attributes['editor'] = $editor;
1031 $this->addElement($editor, $name, $label, $attributes);
1032 $this->assign('editor', $editor);
1034 // include wysiwyg editor js files
1035 // FIXME: This code does not make any sense
1036 $includeWysiwygEditor = FALSE;
1037 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1038 if (!$includeWysiwygEditor) {
1039 $includeWysiwygEditor = TRUE;
1040 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1043 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1046 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1047 $this->addElement('select', $id, $title,
1049 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1052 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1056 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1058 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1061 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1065 public function getRootTitle() {
1069 public function getCompleteTitle() {
1070 return $this->getRootTitle() . $this->getTitle();
1073 static function &getTemplate() {
1074 return self
::$_template;
1077 function addUploadElement($elementName) {
1078 $uploadNames = $this->get('uploadNames');
1079 if (!$uploadNames) {
1080 $uploadNames = array();
1082 if (is_array($elementName)) {
1083 foreach ($elementName as $name) {
1084 if (!in_array($name, $uploadNames)) {
1085 $uploadNames[] = $name;
1090 if (!in_array($elementName, $uploadNames)) {
1091 $uploadNames[] = $elementName;
1094 $this->set('uploadNames', $uploadNames);
1096 $config = CRM_Core_Config
::singleton();
1097 if (!empty($uploadNames)) {
1098 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1102 function buttonType() {
1103 $uploadNames = $this->get('uploadNames');
1104 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1105 $this->assign('buttonType', $buttonType);
1109 function getVar($name) {
1110 return isset($this->$name) ?
$this->$name : NULL;
1113 function setVar($name, $value) {
1114 $this->$name = $value;
1118 * Function to add date
1119 * @param string $name name of the element
1120 * @param string $label label of the element
1121 * @param array $attributes key / value pair
1124 * $attributes = array ( 'addTime' => true,
1125 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1127 * @param boolean $required true if required
1130 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1131 if (!empty($attributes['formatType'])) {
1132 // get actual format
1133 $params = array('name' => $attributes['formatType']);
1136 // cache date information
1138 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1139 if (empty($dateFormat[$key])) {
1140 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1141 $dateFormat[$key] = $values;
1144 $values = $dateFormat[$key];
1147 if ($values['date_format']) {
1148 $attributes['format'] = $values['date_format'];
1151 if (!empty($values['time_format'])) {
1152 $attributes['timeFormat'] = $values['time_format'];
1154 $attributes['startOffset'] = $values['start'];
1155 $attributes['endOffset'] = $values['end'];
1158 $config = CRM_Core_Config
::singleton();
1159 if (empty($attributes['format'])) {
1160 $attributes['format'] = $config->dateInputFormat
;
1163 if (!isset($attributes['startOffset'])) {
1164 $attributes['startOffset'] = 10;
1167 if (!isset($attributes['endOffset'])) {
1168 $attributes['endOffset'] = 10;
1171 $this->add('text', $name, $label, $attributes);
1173 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1175 if (!isset($attributes['timeFormat'])) {
1176 $timeFormat = $config->timeInputFormat
;
1179 $timeFormat = $attributes['timeFormat'];
1182 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1184 $show24Hours = TRUE;
1185 if ($timeFormat == 1) {
1186 $show24Hours = FALSE;
1189 //CRM-6664 -we are having time element name
1190 //in either flat string or an array format.
1191 $elementName = $name . '_time';
1192 if (substr($name, -1) == ']') {
1193 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1196 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1201 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1202 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1203 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1209 * Function that will add date and time
1211 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1212 $addTime = array('addTime' => TRUE);
1213 if (is_array($attributes)) {
1214 $attributes = array_merge($attributes, $addTime);
1217 $attributes = $addTime;
1220 $this->addDate($name, $label, $required, $attributes);
1224 * add a currency and money element to the form
1226 function addMoney($name,
1230 $addCurrency = TRUE,
1231 $currencyName = 'currency',
1232 $defaultCurrency = NULL,
1233 $freezeCurrency = FALSE
1235 $element = $this->add('text', $name, $label, $attributes, $required);
1236 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1239 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1246 * add currency element to the form
1248 function addCurrency($name = 'currency',
1251 $defaultCurrency = NULL,
1252 $freezeCurrency = FALSE
1254 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1255 $options = array('class' => 'crm-select2 eight');
1257 $currencies = array('' => '') +
$currencies;
1258 $options['placeholder'] = ts('- none -');
1260 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1261 if ($freezeCurrency) {
1264 if (!$defaultCurrency) {
1265 $config = CRM_Core_Config
::singleton();
1266 $defaultCurrency = $config->defaultCurrency
;
1268 $this->setDefaults(array($name => $defaultCurrency));
1272 * Create a single or multiple entity ref field
1273 * @param string $name
1274 * @param string $label
1275 * @param array $props mix of html and widget properties, including:
1276 * - select - params to give to select2 widget
1277 * - entity - defaults to contact
1278 * - create - can the user create a new entity on-the-fly?
1279 * Set to TRUE if entity is contact and you want the default profiles,
1280 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1281 * note that permissions are checked automatically
1282 * - api - array of settings for the getlist api wrapper
1283 * note that it accepts a 'params' setting which will be passed to the underlying api
1284 * - placeholder - string
1286 * - class, etc. - other html properties
1287 * @param bool $required
1290 * @return HTML_QuickForm_Element
1292 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1293 require_once "api/api.php";
1294 $config = CRM_Core_Config
::singleton();
1295 // Default properties
1296 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1297 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1298 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1300 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1301 unset($props['create']);
1304 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1306 $defaults = array();
1307 if (!empty($props['multiple'])) {
1308 $defaults['multiple'] = TRUE;
1310 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1312 $this->formatReferenceFieldAttributes($props);
1313 return $this->add('text', $name, $label, $props, $required);
1319 private function formatReferenceFieldAttributes(&$props) {
1320 $props['data-select-params'] = json_encode($props['select']);
1321 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1322 $props['data-api-entity'] = $props['entity'];
1323 if (!empty($props['create'])) {
1324 $props['data-create-links'] = json_encode($props['create']);
1326 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1330 * Convert all date fields within the params to mysql date ready for the
1331 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1332 * and if time is defined it is incorporated
1334 * @param array $params input params from the form
1336 * @todo it would probably be better to work on $this->_params than a passed array
1337 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1340 function convertDateFieldsToMySQL(&$params){
1341 foreach ($this->_dateFields
as $fieldName => $specs){
1342 if(!empty($params[$fieldName])){
1343 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1344 CRM_Utils_Date
::processDate(
1345 $params[$fieldName],
1346 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1350 if(isset($specs['default'])){
1351 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1357 function removeFileRequiredRules($elementName) {
1358 $this->_required
= array_diff($this->_required
, array($elementName));
1359 if (isset($this->_rules
[$elementName])) {
1360 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1361 if ($ruleInfo['type'] == 'uploadedfile') {
1362 unset($this->_rules
[$elementName][$index]);
1365 if (empty($this->_rules
[$elementName])) {
1366 unset($this->_rules
[$elementName]);
1372 * Function that can be defined in Form to override or
1373 * perform specific action on cancel action
1377 function cancelAction() {}
1380 * Helper function to verify that required fields have been filled
1381 * Typically called within the scope of a FormRule function
1383 static function validateMandatoryFields($fields, $values, &$errors) {
1384 foreach ($fields as $name => $fld) {
1385 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1386 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1392 * Get contact if for a form object. Prioritise
1393 * - cid in URL if 0 (on behalf on someoneelse)
1394 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1395 * - logged in user id if it matches the one in the cid in the URL
1396 * - contact id validated from a checksum from a checksum
1397 * - cid from the url if the caller has ACL permission to view
1398 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1400 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1402 function getContactID() {
1403 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1404 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1405 $tempID = $this->_params
['select_contact_id'];
1407 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1408 // event form stores as an indexed array, contribution form not so much...
1409 $tempID = $this->_params
[0]['select_contact_id'];
1412 // force to ignore the authenticated user
1413 if ($tempID === '0' ||
$tempID === 0) {
1414 // we set the cid on the form so that this will be retained for the Confirm page
1415 // in the multi-page form & prevent us returning the $userID when this is called
1417 // we don't really need to set it when $tempID is set because the params have that stored
1418 $this->set('cid', 0);
1422 $userID = $this->getLoggedInUserContactID();
1424 if ($tempID == $userID) {
1428 //check if this is a checksum authentication
1429 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1430 if ($userChecksum) {
1431 //check for anonymous user.
1432 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1437 // check if user has permission, CRM-12062
1438 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1446 * Get the contact id of the logged in user
1448 function getLoggedInUserContactID() {
1449 // check if the user is logged in and has a contact ID
1450 $session = CRM_Core_Session
::singleton();
1451 return $session->get('userID');
1455 * add autoselector field -if user has permission to view contacts
1456 * If adding this to a form you also need to add to the tpl e.g
1458 * {if !empty($selectable)}
1459 * <div class="crm-summary-row">
1460 * <div class="crm-label">{$form.select_contact.label}</div>
1461 * <div class="crm-content">
1462 * {$form.select_contact.html}
1467 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1468 * @param array $autoCompleteField
1470 * @internal param array $field metadata of field to use as selector including
1473 * - url (for ajax lookup)
1475 * @todo add data attributes so we can deal with multiple instances on a form
1477 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1478 $autoCompleteField = array_merge(array(
1479 'id_field' => 'select_contact_id',
1480 'placeholder' => ts('Select someone else ...'),
1481 'show_hide' => TRUE,
1482 'api' => array('params' => array('contact_type' => 'Individual'))
1483 ), $autoCompleteField);
1485 if($this->canUseAjaxContactLookups()) {
1486 $this->assign('selectable', $autoCompleteField['id_field']);
1487 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1489 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1491 'form' => array('autocompletes' => $autoCompleteField),
1492 'ids' => array('profile' => $profiles),
1500 function canUseAjaxContactLookups() {
1501 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1502 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1508 * Add the options appropriate to cid = zero - ie. autocomplete
1510 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1511 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1512 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1513 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1515 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1516 $this->assign('nocid', TRUE);
1517 $profiles = array();
1518 if($this->_values
['custom_pre_id']) {
1519 $profiles[] = $this->_values
['custom_pre_id'];
1521 if($this->_values
['custom_post_id']) {
1522 $profiles[] = $this->_values
['custom_post_id'];
1524 if($onlinePaymentProcessorEnabled) {
1525 $profiles[] = 'billing';
1527 if(!empty($this->_values
)) {
1528 $this->addAutoSelector($profiles);
1533 * Set default values on form for given contact (or no contact defaults)
1535 * @param mixed $profile_id (can be id, or profile name)
1536 * @param integer $contactID
1540 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1542 $defaults = civicrm_api3('profile', 'getsingle', array(
1543 'profile_id' => (array) $profile_id,
1544 'contact_id' => $contactID,
1548 catch (Exception
$e) {
1549 // the try catch block gives us silent failure -not 100% sure this is a good idea
1550 // as silent failures are often worse than noisy ones
1556 * Sets form attribute
1559 function preventAjaxSubmit() {
1560 $this->setAttribute('data-no-ajax-submit', 'true');
1564 * Sets form attribute
1567 function allowAjaxSubmit() {
1568 $this->removeAttribute('data-no-ajax-submit');