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
756 * @param mixed $value value of varaible
758 * @internal param array|string $name name of variable
762 function assign($var, $value = NULL) {
763 self
::$_template->assign($var, $value);
767 * assign value to name in template by reference
770 * @param mixed $value value of varaible
772 * @internal param array|string $name name of variable
776 function assign_by_ref($var, &$value) {
777 self
::$_template->assign_by_ref($var, $value);
781 * appends values to template variables
783 * @param array|string $tpl_var the template variable name(s)
784 * @param mixed $value the value to append
787 function append($tpl_var, $value=NULL, $merge=FALSE) {
788 self
::$_template->append($tpl_var, $value, $merge);
792 * Returns an array containing template variables
794 * @param string $name
796 * @internal param string $type
799 function get_template_vars($name=null) {
800 return self
::$_template->get_template_vars($name);
803 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
805 $attributes = $attributes ?
$attributes : array();
806 $allowClear = !empty($attributes['allowClear']);
807 unset($attributes['allowClear']);
808 $attributes +
= array('id_suffix' => $name);
809 foreach ($values as $key => $var) {
810 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
812 $group = $this->addGroup($options, $name, $title, $separator);
814 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
817 $group->setAttribute('allowClear', TRUE);
822 function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
823 $attributes +
= array('id_suffix' => $id);
825 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
826 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
828 $group = $this->addGroup($choice, $id, $title);
830 $group->setAttribute('allowClear', TRUE);
833 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
837 function addCheckBox($id, $title, $values, $other = NULL,
838 $attributes = NULL, $required = NULL,
839 $javascriptMethod = NULL,
840 $separator = '<br />', $flipValues = FALSE
844 if ($javascriptMethod) {
845 foreach ($values as $key => $var) {
847 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
850 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
855 foreach ($values as $key => $var) {
857 $options[] = $this->createElement('checkbox', $var, NULL, $key);
860 $options[] = $this->createElement('checkbox', $key, NULL, $var);
865 $this->addGroup($options, $id, $title, $separator);
868 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
873 ts('%1 is a required field.', array(1 => $title)),
879 function resetValues() {
880 $data = $this->controller
->container();
881 $data['values'][$this->_name
] = array();
885 * simple shell that derived classes can call to add buttons to
886 * the form with a customized title for the main Submit
888 * @param string $title title of the main button
889 * @param string $nextType
890 * @param string $backType
891 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
893 * @internal param string $type button type for the form after processing
897 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
899 if ($backType != NULL) {
902 'name' => ts('Previous'),
905 if ($nextType != NULL) {
912 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
914 $buttons[] = $nextButton;
916 $this->addButtons($buttons);
919 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
921 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
922 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
924 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
925 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
930 * Adds a select based on field metadata
931 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
932 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
933 * @param $name - field name to go on the form
934 * @param array $props - mix of html attributes and special properties, namely
935 * - entity (api entity name, can usually be inferred automatically from the form class)
936 * - field (field name - only needed if different from name used on the form)
937 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
938 * - placeholder - set to NULL to disable
940 * @param bool $required
941 * @throws CRM_Core_Exception
942 * @return HTML_QuickForm_Element
944 function addSelect($name, $props = array(), $required = FALSE) {
945 if (!isset($props['entity'])) {
946 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
948 if (!isset($props['field'])) {
949 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
951 $info = civicrm_api3($props['entity'], 'getoptions', array(
952 'field' => $props['field'],
953 'options' => array('metadata' => array('fields'))
956 $options = $info['values'];
957 if (!array_key_exists('placeholder', $props)) {
958 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
960 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
961 $options = array('' => '') +
$options;
963 // Handle custom field
964 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
965 list(, $id) = explode('_', $name);
966 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
967 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
968 $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);
972 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
974 $uniqueName === $props['field'] ||
975 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
976 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
981 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
982 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
984 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
985 $props['data-api-entity'] = $props['entity'];
986 $props['data-api-field'] = $props['field'];
987 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
988 return $this->add('select', $name, $label, $options, $required, $props);
992 * Add a widget for selecting/editing/creating/copying a profile form
994 * @param string $name HTML form-element name
995 * @param string $label Printable label
996 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
997 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
998 * @param array $entities
1000 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
1002 // FIXME: Instead of adhoc serialization, use a single json_encode()
1003 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
1004 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1005 $this->add('text', $name, $label, array(
1006 'class' => 'crm-profile-selector',
1007 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1008 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1009 'data-entities' => json_encode($entities),
1013 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1014 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1015 // 2. Based on the option, initialise proper editor
1016 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1019 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1020 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1022 if (!$editor ||
$forceTextarea) {
1023 $editor = 'textarea';
1025 if ($editor == 'joomla default editor') {
1026 $editor = 'joomlaeditor';
1029 if ($editor == 'drupal default editor') {
1030 $editor = 'drupalwysiwyg';
1033 //lets add the editor as a attribute
1034 $attributes['editor'] = $editor;
1036 $this->addElement($editor, $name, $label, $attributes);
1037 $this->assign('editor', $editor);
1039 // include wysiwyg editor js files
1040 // FIXME: This code does not make any sense
1041 $includeWysiwygEditor = FALSE;
1042 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1043 if (!$includeWysiwygEditor) {
1044 $includeWysiwygEditor = TRUE;
1045 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1048 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1051 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1052 $this->addElement('select', $id, $title,
1054 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1057 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1061 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1063 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1066 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1070 public function getRootTitle() {
1074 public function getCompleteTitle() {
1075 return $this->getRootTitle() . $this->getTitle();
1078 static function &getTemplate() {
1079 return self
::$_template;
1082 function addUploadElement($elementName) {
1083 $uploadNames = $this->get('uploadNames');
1084 if (!$uploadNames) {
1085 $uploadNames = array();
1087 if (is_array($elementName)) {
1088 foreach ($elementName as $name) {
1089 if (!in_array($name, $uploadNames)) {
1090 $uploadNames[] = $name;
1095 if (!in_array($elementName, $uploadNames)) {
1096 $uploadNames[] = $elementName;
1099 $this->set('uploadNames', $uploadNames);
1101 $config = CRM_Core_Config
::singleton();
1102 if (!empty($uploadNames)) {
1103 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1107 function buttonType() {
1108 $uploadNames = $this->get('uploadNames');
1109 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1110 $this->assign('buttonType', $buttonType);
1114 function getVar($name) {
1115 return isset($this->$name) ?
$this->$name : NULL;
1118 function setVar($name, $value) {
1119 $this->$name = $value;
1123 * Function to add date
1124 * @param string $name name of the element
1125 * @param string $label label of the element
1126 * @param array $attributes key / value pair
1129 * $attributes = array ( 'addTime' => true,
1130 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1132 * @param boolean $required true if required
1135 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1136 if (!empty($attributes['formatType'])) {
1137 // get actual format
1138 $params = array('name' => $attributes['formatType']);
1141 // cache date information
1143 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1144 if (empty($dateFormat[$key])) {
1145 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1146 $dateFormat[$key] = $values;
1149 $values = $dateFormat[$key];
1152 if ($values['date_format']) {
1153 $attributes['format'] = $values['date_format'];
1156 if (!empty($values['time_format'])) {
1157 $attributes['timeFormat'] = $values['time_format'];
1159 $attributes['startOffset'] = $values['start'];
1160 $attributes['endOffset'] = $values['end'];
1163 $config = CRM_Core_Config
::singleton();
1164 if (empty($attributes['format'])) {
1165 $attributes['format'] = $config->dateInputFormat
;
1168 if (!isset($attributes['startOffset'])) {
1169 $attributes['startOffset'] = 10;
1172 if (!isset($attributes['endOffset'])) {
1173 $attributes['endOffset'] = 10;
1176 $this->add('text', $name, $label, $attributes);
1178 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1180 if (!isset($attributes['timeFormat'])) {
1181 $timeFormat = $config->timeInputFormat
;
1184 $timeFormat = $attributes['timeFormat'];
1187 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1189 $show24Hours = TRUE;
1190 if ($timeFormat == 1) {
1191 $show24Hours = FALSE;
1194 //CRM-6664 -we are having time element name
1195 //in either flat string or an array format.
1196 $elementName = $name . '_time';
1197 if (substr($name, -1) == ']') {
1198 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1201 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1206 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1207 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1208 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1214 * Function that will add date and time
1216 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1217 $addTime = array('addTime' => TRUE);
1218 if (is_array($attributes)) {
1219 $attributes = array_merge($attributes, $addTime);
1222 $attributes = $addTime;
1225 $this->addDate($name, $label, $required, $attributes);
1229 * add a currency and money element to the form
1231 function addMoney($name,
1235 $addCurrency = TRUE,
1236 $currencyName = 'currency',
1237 $defaultCurrency = NULL,
1238 $freezeCurrency = FALSE
1240 $element = $this->add('text', $name, $label, $attributes, $required);
1241 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1244 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1251 * add currency element to the form
1253 function addCurrency($name = 'currency',
1256 $defaultCurrency = NULL,
1257 $freezeCurrency = FALSE
1259 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1260 $options = array('class' => 'crm-select2 eight');
1262 $currencies = array('' => '') +
$currencies;
1263 $options['placeholder'] = ts('- none -');
1265 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1266 if ($freezeCurrency) {
1269 if (!$defaultCurrency) {
1270 $config = CRM_Core_Config
::singleton();
1271 $defaultCurrency = $config->defaultCurrency
;
1273 $this->setDefaults(array($name => $defaultCurrency));
1277 * Create a single or multiple entity ref field
1278 * @param string $name
1279 * @param string $label
1280 * @param array $props mix of html and widget properties, including:
1281 * - select - params to give to select2 widget
1282 * - entity - defaults to contact
1283 * - create - can the user create a new entity on-the-fly?
1284 * Set to TRUE if entity is contact and you want the default profiles,
1285 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1286 * note that permissions are checked automatically
1287 * - api - array of settings for the getlist api wrapper
1288 * note that it accepts a 'params' setting which will be passed to the underlying api
1289 * - placeholder - string
1291 * - class, etc. - other html properties
1292 * @param bool $required
1295 * @return HTML_QuickForm_Element
1297 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1298 require_once "api/api.php";
1299 $config = CRM_Core_Config
::singleton();
1300 // Default properties
1301 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1302 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1303 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1305 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1306 unset($props['create']);
1309 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1311 $defaults = array();
1312 if (!empty($props['multiple'])) {
1313 $defaults['multiple'] = TRUE;
1315 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1317 $this->formatReferenceFieldAttributes($props);
1318 return $this->add('text', $name, $label, $props, $required);
1324 private function formatReferenceFieldAttributes(&$props) {
1325 $props['data-select-params'] = json_encode($props['select']);
1326 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1327 $props['data-api-entity'] = $props['entity'];
1328 if (!empty($props['create'])) {
1329 $props['data-create-links'] = json_encode($props['create']);
1331 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1335 * Convert all date fields within the params to mysql date ready for the
1336 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1337 * and if time is defined it is incorporated
1339 * @param array $params input params from the form
1341 * @todo it would probably be better to work on $this->_params than a passed array
1342 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1345 function convertDateFieldsToMySQL(&$params){
1346 foreach ($this->_dateFields
as $fieldName => $specs){
1347 if(!empty($params[$fieldName])){
1348 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1349 CRM_Utils_Date
::processDate(
1350 $params[$fieldName],
1351 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1355 if(isset($specs['default'])){
1356 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1362 function removeFileRequiredRules($elementName) {
1363 $this->_required
= array_diff($this->_required
, array($elementName));
1364 if (isset($this->_rules
[$elementName])) {
1365 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1366 if ($ruleInfo['type'] == 'uploadedfile') {
1367 unset($this->_rules
[$elementName][$index]);
1370 if (empty($this->_rules
[$elementName])) {
1371 unset($this->_rules
[$elementName]);
1377 * Function that can be defined in Form to override or
1378 * perform specific action on cancel action
1382 function cancelAction() {}
1385 * Helper function to verify that required fields have been filled
1386 * Typically called within the scope of a FormRule function
1388 static function validateMandatoryFields($fields, $values, &$errors) {
1389 foreach ($fields as $name => $fld) {
1390 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1391 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1397 * Get contact if for a form object. Prioritise
1398 * - cid in URL if 0 (on behalf on someoneelse)
1399 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1400 * - logged in user id if it matches the one in the cid in the URL
1401 * - contact id validated from a checksum from a checksum
1402 * - cid from the url if the caller has ACL permission to view
1403 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1405 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1407 function getContactID() {
1408 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1409 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1410 $tempID = $this->_params
['select_contact_id'];
1412 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1413 // event form stores as an indexed array, contribution form not so much...
1414 $tempID = $this->_params
[0]['select_contact_id'];
1417 // force to ignore the authenticated user
1418 if ($tempID === '0' ||
$tempID === 0) {
1419 // we set the cid on the form so that this will be retained for the Confirm page
1420 // in the multi-page form & prevent us returning the $userID when this is called
1422 // we don't really need to set it when $tempID is set because the params have that stored
1423 $this->set('cid', 0);
1427 $userID = $this->getLoggedInUserContactID();
1429 if ($tempID == $userID) {
1433 //check if this is a checksum authentication
1434 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1435 if ($userChecksum) {
1436 //check for anonymous user.
1437 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1442 // check if user has permission, CRM-12062
1443 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1451 * Get the contact id of the logged in user
1453 function getLoggedInUserContactID() {
1454 // check if the user is logged in and has a contact ID
1455 $session = CRM_Core_Session
::singleton();
1456 return $session->get('userID');
1460 * add autoselector field -if user has permission to view contacts
1461 * If adding this to a form you also need to add to the tpl e.g
1463 * {if !empty($selectable)}
1464 * <div class="crm-summary-row">
1465 * <div class="crm-label">{$form.select_contact.label}</div>
1466 * <div class="crm-content">
1467 * {$form.select_contact.html}
1472 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1473 * @param array $autoCompleteField
1475 * @internal param array $field metadata of field to use as selector including
1478 * - url (for ajax lookup)
1480 * @todo add data attributes so we can deal with multiple instances on a form
1482 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1483 $autoCompleteField = array_merge(array(
1484 'id_field' => 'select_contact_id',
1485 'placeholder' => ts('Select someone else ...'),
1486 'show_hide' => TRUE,
1487 'api' => array('params' => array('contact_type' => 'Individual'))
1488 ), $autoCompleteField);
1490 if($this->canUseAjaxContactLookups()) {
1491 $this->assign('selectable', $autoCompleteField['id_field']);
1492 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1494 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1496 'form' => array('autocompletes' => $autoCompleteField),
1497 'ids' => array('profile' => $profiles),
1505 function canUseAjaxContactLookups() {
1506 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1507 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1513 * Add the options appropriate to cid = zero - ie. autocomplete
1515 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1516 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1517 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1518 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1520 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1521 $this->assign('nocid', TRUE);
1522 $profiles = array();
1523 if($this->_values
['custom_pre_id']) {
1524 $profiles[] = $this->_values
['custom_pre_id'];
1526 if($this->_values
['custom_post_id']) {
1527 $profiles[] = $this->_values
['custom_post_id'];
1529 if($onlinePaymentProcessorEnabled) {
1530 $profiles[] = 'billing';
1532 if(!empty($this->_values
)) {
1533 $this->addAutoSelector($profiles);
1538 * Set default values on form for given contact (or no contact defaults)
1540 * @param mixed $profile_id (can be id, or profile name)
1541 * @param integer $contactID
1545 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1547 $defaults = civicrm_api3('profile', 'getsingle', array(
1548 'profile_id' => (array) $profile_id,
1549 'contact_id' => $contactID,
1553 catch (Exception
$e) {
1554 // the try catch block gives us silent failure -not 100% sure this is a good idea
1555 // as silent failures are often worse than noisy ones
1561 * Sets form attribute
1564 function preventAjaxSubmit() {
1565 $this->setAttribute('data-no-ajax-submit', 'true');
1569 * Sets form attribute
1572 function allowAjaxSubmit() {
1573 $this->removeAttribute('data-no-ajax-submit');