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 * @var CRM_Core_Controller
126 * constants for attributes for various form elements
127 * attempt to standardize on the number of variations that we
128 * use of the below form elements
132 CONST ATTR_SPACING
= ' ';
135 * All checkboxes are defined with a common prefix. This allows us to
136 * have the same javascript to check / clear all the checkboxes etc
137 * If u have multiple groups of checkboxes, you will need to give them different
138 * ids to avoid potential name collision
140 * @var const string / int
142 CONST CB_PREFIX
= 'mark_x_', CB_PREFIY
= 'mark_y_', CB_PREFIZ
= 'mark_z_', CB_PREFIX_LEN
= 7;
145 * Constructor for the basic form page
147 * We should not use QuickForm directly. This class provides a lot
148 * of default convenient functions, rules and buttons
150 * @param object $state State associated with this form
151 * @param \const|\enum $action The mode the form is operating in (None/Create/View/Update/Delete)
152 * @param string $method The type of http method used (GET/POST)
153 * @param string $name The name of the form if different from class name
155 * @return \CRM_Core_Form
158 function __construct(
160 $action = CRM_Core_Action
::NONE
,
166 $this->_name
= $name;
169 $this->_name
= CRM_Utils_String
::getClassName(CRM_Utils_System
::getClassName($this));
172 $this->HTML_QuickForm_Page($this->_name
, $method);
174 $this->_state
=& $state;
176 $this->_state
->setName($this->_name
);
178 $this->_action
= (int) $action;
180 $this->registerRules();
182 // let the constructor initialize this, should happen only once
183 if (!isset(self
::$_template)) {
184 self
::$_template = CRM_Core_Smarty
::singleton();
187 $this->assign('snippet', CRM_Utils_Array
::value('snippet', $_GET));
190 static function generateID() {
194 * register all the standard rules that most forms potentially use
200 function registerRules() {
201 static $rules = array(
202 'title', 'longTitle', 'variable', 'qfVariable',
203 'phone', 'integer', 'query',
205 'domain', 'numberOfDigit',
206 'date', 'currentDate',
207 'asciiFile', 'htmlFile', 'utf8File',
208 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
209 'xssString', 'fileExists', 'autocomplete', 'validContact',
212 foreach ($rules as $rule) {
213 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
218 * Simple easy to use wrapper around addElement. Deal with
219 * simple validation rules
223 * @param string $label
224 * @param string $attributes
225 * @param bool $required
228 * @internal param \type $string of html element to be added
229 * @internal param \name $string of the html element
230 * @internal param \display $string label for the html element
231 * @internal param \attributes $string used for this element.
232 * These are not default values
233 * @internal param \is $bool this a required field
235 * @return HTML_QuickForm_Element could be an error object
238 function &add($type, $name, $label = '',
239 $attributes = '', $required = FALSE, $extra = NULL
241 // Normalize this property
242 if ($type == 'select' && is_array($extra) && !empty($extra['multiple'])) {
243 $extra['multiple'] = 'multiple';
245 $element = $this->addElement($type, $name, $label, $attributes, $extra);
246 if (HTML_QuickForm
::isError($element)) {
247 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
251 if ($type == 'file') {
252 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
255 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
257 if (HTML_QuickForm
::isError($error)) {
258 CRM_Core_Error
::fatal(HTML_QuickForm
::errorMessage($element));
266 * This function is called before buildForm. Any pre-processing that
267 * needs to be done for buildForm should be done here
269 * This is a virtual function and should be redefined if needed
276 function preProcess() {}
279 * This function is called after the form is validated. Any
280 * processing of form state etc should be done in this function.
281 * Typically all processing associated with a form should be done
282 * here and relevant state should be stored in the session
284 * This is a virtual function and should be redefined if needed
291 function postProcess() {}
294 * This function is just a wrapper, so that we can call all the hook functions
296 function mainProcess() {
297 $this->postProcess();
298 $this->postProcessHook();
300 // Respond with JSON if in AJAX context (also support legacy value '6')
301 if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty
::PRINT_JSON
, 6))) {
302 $this->ajaxResponse
['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller
->getButtonName());
303 $this->ajaxResponse
['action'] = $this->_action
;
304 if (isset($this->_id
) ||
isset($this->id
)) {
305 $this->ajaxResponse
['id'] = isset($this->id
) ?
$this->id
: $this->_id
;
307 CRM_Core_Page_AJAX
::returnJsonResponse($this->ajaxResponse
);
312 * The postProcess hook is typically called by the framework
313 * However in a few cases, the form exits or redirects early in which
314 * case it needs to call this function so other modules can do the needful
315 * Calling this function directly should be avoided if possible. In general a
316 * better way is to do setUserContext so the framework does the redirect
319 function postProcessHook() {
320 CRM_Utils_Hook
::postProcess(get_class($this), $this);
324 * This virtual function is used to build the form. It replaces the
325 * buildForm associated with QuickForm_Page. This allows us to put
326 * preProcess in front of the actual form building routine
333 function buildQuickForm() {}
336 * This virtual function is used to set the default values of
337 * various form elements
341 * @return array reference to the array of default values
344 function setDefaultValues() {}
347 * This is a virtual function that adds group and global rules to
348 * the form. Keeping it distinct from the form to keep code small
349 * and localized in the form building code
356 function addRules() {}
358 function validate() {
359 $error = parent
::validate();
361 $hookErrors = CRM_Utils_Hook
::validate(
363 $this->_submitValues
,
368 if (!is_array($hookErrors)) {
369 $hookErrors = array();
372 CRM_Utils_Hook
::validateForm(
374 $this->_submitValues
,
380 if (!empty($hookErrors)) {
381 $this->_errors +
= $hookErrors;
384 return (0 == count($this->_errors
));
388 * Core function that builds the form. We redefine this function
389 * here and expect all CRM forms to build their form in the function
393 function buildForm() {
394 $this->_formBuilt
= TRUE;
398 $this->assign('translatePermission', CRM_Core_Permission
::check('translate CiviCRM'));
401 $this->controller
->_key
&&
402 $this->controller
->_generateQFKey
404 $this->addElement('hidden', 'qfKey', $this->controller
->_key
);
405 $this->assign('qfKey', $this->controller
->_key
);
409 // _generateQFKey suppresses the qfKey generation on form snippets that
410 // are part of other forms, hence we use that to avoid adding entryURL
411 if ($this->controller
->_generateQFKey
&& $this->controller
->_entryURL
) {
412 $this->addElement('hidden', 'entryURL', $this->controller
->_entryURL
);
415 $this->buildQuickForm();
417 $defaults = $this->setDefaultValues();
418 unset($defaults['qfKey']);
420 if (!empty($defaults)) {
421 $this->setDefaults($defaults);
424 // call the form hook
425 // also call the hook function so any modules can set thier own custom defaults
426 // the user can do both the form and set default values with this hook
427 CRM_Utils_Hook
::buildForm(get_class($this), $this);
431 //Set html data-attribute to enable warning user of unsaved changes
432 if ($this->unsavedChangesWarn
=== true
433 ||
(!isset($this->unsavedChangesWarn
)
434 && ($this->_action
& CRM_Core_Action
::ADD ||
$this->_action
& CRM_Core_Action
::UPDATE
)
437 $this->setAttribute('data-warn-changes', 'true');
442 * Add default Next / Back buttons
444 * @param array array of associative arrays in the order in which the buttons should be
445 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
446 * The base form class will define a bunch of static arrays for commonly used
454 function addButtons($params) {
457 foreach ($params as $button) {
458 $js = CRM_Utils_Array
::value('js', $button);
459 $isDefault = CRM_Utils_Array
::value('isDefault', $button, FALSE);
461 $attrs = array('class' => 'form-submit default');
464 $attrs = array('class' => 'form-submit');
468 $attrs = array_merge($js, $attrs);
471 if ($button['type'] === 'cancel') {
472 $attrs['class'] .= ' cancel';
475 if ($button['type'] === 'reset') {
476 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
479 if (!empty($button['subName'])) {
480 $buttonName = $this->getButtonName($button['type'], $button['subName']);
483 $buttonName = $this->getButtonName($button['type']);
486 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
487 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
489 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
491 if (!empty($button['isDefault'])) {
492 $this->setDefaultAction($button['type']);
495 // if button type is upload, set the enctype
496 if ($button['type'] == 'upload') {
497 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
498 $this->setMaxFileSize();
501 // hack - addGroup uses an array to express variable spacing, read from the last element
502 $spacing[] = CRM_Utils_Array
::value('spacing', $button, self
::ATTR_SPACING
);
504 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
508 * getter function for Name
518 * getter function for State
523 function &getState() {
524 return $this->_state
;
528 * getter function for StateType
533 function getStateType() {
534 return $this->_state
->getType();
538 * getter function for title. Should be over-ridden by derived class
543 function getTitle() {
544 return $this->_title ?
$this->_title
: ts('ERROR: Title is not Set');
548 * setter function for title.
550 * @param string $title the title of the form
555 function setTitle($title) {
556 $this->_title
= $title;
560 * Setter function for options
567 function setOptions($options) {
568 $this->_options
= $options;
572 * getter function for link.
578 $config = CRM_Core_Config
::singleton();
579 return CRM_Utils_System
::url($_GET[$config->userFrameworkURLVar
],
580 '_qf_' . $this->_name
. '_display=true'
585 * boolean function to determine if this is a one form page
590 function isSimpleForm() {
591 return $this->_state
->getType() & (CRM_Core_State
::START | CRM_Core_State
::FINISH
);
595 * getter function for Form Action
600 function getFormAction() {
601 return $this->_attributes
['action'];
605 * setter function for Form Action
612 function setFormAction($action) {
613 $this->_attributes
['action'] = $action;
617 * render form and return contents
622 function toSmarty() {
623 $renderer = $this->getRenderer();
624 $this->accept($renderer);
625 $content = $renderer->toArray();
626 $content['formName'] = $this->getName();
631 * getter function for renderer. If renderer is not set
632 * create one and initialize it
637 function &getRenderer() {
638 if (!isset($this->_renderer
)) {
639 $this->_renderer
= CRM_Core_Form_Renderer
::singleton();
641 return $this->_renderer
;
645 * Use the form name to create the tpl file name
650 function getTemplateFileName() {
651 $ext = CRM_Extension_System
::singleton()->getMapper();
652 if ($ext->isExtensionClass(CRM_Utils_System
::getClassName($this))) {
653 $filename = $ext->getTemplateName(CRM_Utils_System
::getClassName($this));
654 $tplname = $ext->getTemplatePath(CRM_Utils_System
::getClassName($this)) . DIRECTORY_SEPARATOR
. $filename;
657 $tplname = str_replace('_',
659 CRM_Utils_System
::getClassName($this)
666 * A wrapper for getTemplateFileName that includes calling the hook to
667 * prevent us from having to copy & paste the logic of calling the hook
669 function getHookedTemplateFileName() {
670 $pageTemplateFile = $this->getTemplateFileName();
671 CRM_Utils_Hook
::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
672 return $pageTemplateFile;
676 * Default extra tpl file basically just replaces .tpl with .extra.tpl
677 * i.e. we dont override
682 function overrideExtraTemplateFileName() {
687 * Error reporting mechanism
689 * @param string $message Error Message
690 * @param int $code Error Code
691 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
696 function error($message, $code = NULL, $dao = NULL) {
698 $dao->query('ROLLBACK');
701 $error = CRM_Core_Error
::singleton();
703 $error->push($code, $message);
707 * Store the variable with the value in the form scope
709 * @param string name : name of the variable
710 * @param mixed value : value of the variable
717 function set($name, $value) {
718 $this->controller
->set($name, $value);
722 * Get the variable from the form scope
724 * @param string name : name of the variable
731 function get($name) {
732 return $this->controller
->get($name);
741 function getAction() {
742 return $this->_action
;
748 * @param int $action the mode we want to set the form
753 function setAction($action) {
754 $this->_action
= $action;
758 * assign value to name in template
761 * @param mixed $value value of varaible
763 * @internal param array|string $name name of variable
767 function assign($var, $value = NULL) {
768 self
::$_template->assign($var, $value);
772 * assign value to name in template by reference
775 * @param mixed $value value of varaible
777 * @internal param array|string $name name of variable
781 function assign_by_ref($var, &$value) {
782 self
::$_template->assign_by_ref($var, $value);
786 * appends values to template variables
788 * @param array|string $tpl_var the template variable name(s)
789 * @param mixed $value the value to append
792 function append($tpl_var, $value=NULL, $merge=FALSE) {
793 self
::$_template->append($tpl_var, $value, $merge);
797 * Returns an array containing template variables
799 * @param string $name
801 * @internal param string $type
804 function get_template_vars($name=null) {
805 return self
::$_template->get_template_vars($name);
808 function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
810 $attributes = $attributes ?
$attributes : array();
811 $allowClear = !empty($attributes['allowClear']);
812 unset($attributes['allowClear']);
813 $attributes +
= array('id_suffix' => $name);
814 foreach ($values as $key => $var) {
815 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
817 $group = $this->addGroup($options, $name, $title, $separator);
819 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
822 $group->setAttribute('allowClear', TRUE);
827 function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
828 $attributes +
= array('id_suffix' => $id);
830 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
831 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
833 $group = $this->addGroup($choice, $id, $title);
835 $group->setAttribute('allowClear', TRUE);
838 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
842 function addCheckBox($id, $title, $values, $other = NULL,
843 $attributes = NULL, $required = NULL,
844 $javascriptMethod = NULL,
845 $separator = '<br />', $flipValues = FALSE
849 if ($javascriptMethod) {
850 foreach ($values as $key => $var) {
852 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
855 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
860 foreach ($values as $key => $var) {
862 $options[] = $this->createElement('checkbox', $var, NULL, $key);
865 $options[] = $this->createElement('checkbox', $key, NULL, $var);
870 $this->addGroup($options, $id, $title, $separator);
873 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
878 ts('%1 is a required field.', array(1 => $title)),
884 function resetValues() {
885 $data = $this->controller
->container();
886 $data['values'][$this->_name
] = array();
890 * simple shell that derived classes can call to add buttons to
891 * the form with a customized title for the main Submit
893 * @param string $title title of the main button
894 * @param string $nextType
895 * @param string $backType
896 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
898 * @internal param string $type button type for the form after processing
902 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
904 if ($backType != NULL) {
907 'name' => ts('Previous'),
910 if ($nextType != NULL) {
917 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
919 $buttons[] = $nextButton;
921 $this->addButtons($buttons);
924 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
926 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
927 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
929 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
930 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
935 * Adds a select based on field metadata
936 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
937 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
938 * @param $name - field name to go on the form
939 * @param array $props - mix of html attributes and special properties, namely
940 * - entity (api entity name, can usually be inferred automatically from the form class)
941 * - field (field name - only needed if different from name used on the form)
942 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
943 * - placeholder - set to NULL to disable
945 * @param bool $required
946 * @throws CRM_Core_Exception
947 * @return HTML_QuickForm_Element
949 function addSelect($name, $props = array(), $required = FALSE) {
950 if (!isset($props['entity'])) {
951 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
953 if (!isset($props['field'])) {
954 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
956 $info = civicrm_api3($props['entity'], 'getoptions', array(
957 'field' => $props['field'],
958 'options' => array('metadata' => array('fields'))
961 $options = $info['values'];
962 if (!array_key_exists('placeholder', $props)) {
963 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
965 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
966 $options = array('' => '') +
$options;
968 // Handle custom field
969 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
970 list(, $id) = explode('_', $name);
971 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
972 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
973 $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);
977 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
979 $uniqueName === $props['field'] ||
980 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
981 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
986 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
987 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
989 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
990 $props['data-api-entity'] = $props['entity'];
991 $props['data-api-field'] = $props['field'];
992 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
993 return $this->add('select', $name, $label, $options, $required, $props);
997 * Add a widget for selecting/editing/creating/copying a profile form
999 * @param string $name HTML form-element name
1000 * @param string $label Printable label
1001 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
1002 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
1003 * @param array $entities
1005 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
1007 // FIXME: Instead of adhoc serialization, use a single json_encode()
1008 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
1009 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1010 $this->add('text', $name, $label, array(
1011 'class' => 'crm-profile-selector',
1012 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1013 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1014 'data-entities' => json_encode($entities),
1018 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1019 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1020 // 2. Based on the option, initialise proper editor
1021 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1024 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1025 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1027 if (!$editor ||
$forceTextarea) {
1028 $editor = 'textarea';
1030 if ($editor == 'joomla default editor') {
1031 $editor = 'joomlaeditor';
1034 if ($editor == 'drupal default editor') {
1035 $editor = 'drupalwysiwyg';
1038 //lets add the editor as a attribute
1039 $attributes['editor'] = $editor;
1041 $this->addElement($editor, $name, $label, $attributes);
1042 $this->assign('editor', $editor);
1044 // include wysiwyg editor js files
1045 // FIXME: This code does not make any sense
1046 $includeWysiwygEditor = FALSE;
1047 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1048 if (!$includeWysiwygEditor) {
1049 $includeWysiwygEditor = TRUE;
1050 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1053 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1056 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1057 $this->addElement('select', $id, $title,
1059 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1062 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1066 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1068 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1071 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1075 public function getRootTitle() {
1079 public function getCompleteTitle() {
1080 return $this->getRootTitle() . $this->getTitle();
1083 static function &getTemplate() {
1084 return self
::$_template;
1087 function addUploadElement($elementName) {
1088 $uploadNames = $this->get('uploadNames');
1089 if (!$uploadNames) {
1090 $uploadNames = array();
1092 if (is_array($elementName)) {
1093 foreach ($elementName as $name) {
1094 if (!in_array($name, $uploadNames)) {
1095 $uploadNames[] = $name;
1100 if (!in_array($elementName, $uploadNames)) {
1101 $uploadNames[] = $elementName;
1104 $this->set('uploadNames', $uploadNames);
1106 $config = CRM_Core_Config
::singleton();
1107 if (!empty($uploadNames)) {
1108 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1112 function buttonType() {
1113 $uploadNames = $this->get('uploadNames');
1114 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1115 $this->assign('buttonType', $buttonType);
1119 function getVar($name) {
1120 return isset($this->$name) ?
$this->$name : NULL;
1123 function setVar($name, $value) {
1124 $this->$name = $value;
1128 * Function to add date
1129 * @param string $name name of the element
1130 * @param string $label label of the element
1131 * @param array $attributes key / value pair
1134 * $attributes = array ( 'addTime' => true,
1135 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1137 * @param boolean $required true if required
1140 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1141 if (!empty($attributes['formatType'])) {
1142 // get actual format
1143 $params = array('name' => $attributes['formatType']);
1146 // cache date information
1148 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1149 if (empty($dateFormat[$key])) {
1150 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1151 $dateFormat[$key] = $values;
1154 $values = $dateFormat[$key];
1157 if ($values['date_format']) {
1158 $attributes['format'] = $values['date_format'];
1161 if (!empty($values['time_format'])) {
1162 $attributes['timeFormat'] = $values['time_format'];
1164 $attributes['startOffset'] = $values['start'];
1165 $attributes['endOffset'] = $values['end'];
1168 $config = CRM_Core_Config
::singleton();
1169 if (empty($attributes['format'])) {
1170 $attributes['format'] = $config->dateInputFormat
;
1173 if (!isset($attributes['startOffset'])) {
1174 $attributes['startOffset'] = 10;
1177 if (!isset($attributes['endOffset'])) {
1178 $attributes['endOffset'] = 10;
1181 $this->add('text', $name, $label, $attributes);
1183 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1185 if (!isset($attributes['timeFormat'])) {
1186 $timeFormat = $config->timeInputFormat
;
1189 $timeFormat = $attributes['timeFormat'];
1192 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1194 $show24Hours = TRUE;
1195 if ($timeFormat == 1) {
1196 $show24Hours = FALSE;
1199 //CRM-6664 -we are having time element name
1200 //in either flat string or an array format.
1201 $elementName = $name . '_time';
1202 if (substr($name, -1) == ']') {
1203 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1206 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1211 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1212 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1213 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1219 * Function that will add date and time
1221 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1222 $addTime = array('addTime' => TRUE);
1223 if (is_array($attributes)) {
1224 $attributes = array_merge($attributes, $addTime);
1227 $attributes = $addTime;
1230 $this->addDate($name, $label, $required, $attributes);
1234 * add a currency and money element to the form
1236 function addMoney($name,
1240 $addCurrency = TRUE,
1241 $currencyName = 'currency',
1242 $defaultCurrency = NULL,
1243 $freezeCurrency = FALSE
1245 $element = $this->add('text', $name, $label, $attributes, $required);
1246 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1249 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1256 * add currency element to the form
1258 function addCurrency($name = 'currency',
1261 $defaultCurrency = NULL,
1262 $freezeCurrency = FALSE
1264 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1265 $options = array('class' => 'crm-select2 eight');
1267 $currencies = array('' => '') +
$currencies;
1268 $options['placeholder'] = ts('- none -');
1270 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1271 if ($freezeCurrency) {
1274 if (!$defaultCurrency) {
1275 $config = CRM_Core_Config
::singleton();
1276 $defaultCurrency = $config->defaultCurrency
;
1278 $this->setDefaults(array($name => $defaultCurrency));
1282 * Create a single or multiple entity ref field
1283 * @param string $name
1284 * @param string $label
1285 * @param array $props mix of html and widget properties, including:
1286 * - select - params to give to select2 widget
1287 * - entity - defaults to contact
1288 * - create - can the user create a new entity on-the-fly?
1289 * Set to TRUE if entity is contact and you want the default profiles,
1290 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1291 * note that permissions are checked automatically
1292 * - api - array of settings for the getlist api wrapper
1293 * note that it accepts a 'params' setting which will be passed to the underlying api
1294 * - placeholder - string
1296 * - class, etc. - other html properties
1297 * @param bool $required
1300 * @return HTML_QuickForm_Element
1302 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1303 require_once "api/api.php";
1304 $config = CRM_Core_Config
::singleton();
1305 // Default properties
1306 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1307 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1308 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1310 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1311 unset($props['create']);
1314 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1316 $defaults = array();
1317 if (!empty($props['multiple'])) {
1318 $defaults['multiple'] = TRUE;
1320 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1322 $this->formatReferenceFieldAttributes($props);
1323 return $this->add('text', $name, $label, $props, $required);
1329 private function formatReferenceFieldAttributes(&$props) {
1330 $props['data-select-params'] = json_encode($props['select']);
1331 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1332 $props['data-api-entity'] = $props['entity'];
1333 if (!empty($props['create'])) {
1334 $props['data-create-links'] = json_encode($props['create']);
1336 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1340 * Convert all date fields within the params to mysql date ready for the
1341 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1342 * and if time is defined it is incorporated
1344 * @param array $params input params from the form
1346 * @todo it would probably be better to work on $this->_params than a passed array
1347 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1350 function convertDateFieldsToMySQL(&$params){
1351 foreach ($this->_dateFields
as $fieldName => $specs){
1352 if(!empty($params[$fieldName])){
1353 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1354 CRM_Utils_Date
::processDate(
1355 $params[$fieldName],
1356 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1360 if(isset($specs['default'])){
1361 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1367 function removeFileRequiredRules($elementName) {
1368 $this->_required
= array_diff($this->_required
, array($elementName));
1369 if (isset($this->_rules
[$elementName])) {
1370 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1371 if ($ruleInfo['type'] == 'uploadedfile') {
1372 unset($this->_rules
[$elementName][$index]);
1375 if (empty($this->_rules
[$elementName])) {
1376 unset($this->_rules
[$elementName]);
1382 * Function that can be defined in Form to override or
1383 * perform specific action on cancel action
1387 function cancelAction() {}
1390 * Helper function to verify that required fields have been filled
1391 * Typically called within the scope of a FormRule function
1393 static function validateMandatoryFields($fields, $values, &$errors) {
1394 foreach ($fields as $name => $fld) {
1395 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1396 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1402 * Get contact if for a form object. Prioritise
1403 * - cid in URL if 0 (on behalf on someoneelse)
1404 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1405 * - logged in user id if it matches the one in the cid in the URL
1406 * - contact id validated from a checksum from a checksum
1407 * - cid from the url if the caller has ACL permission to view
1408 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1410 * @return mixed NULL|integer
1412 function getContactID() {
1413 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1414 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1415 $tempID = $this->_params
['select_contact_id'];
1417 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1418 // event form stores as an indexed array, contribution form not so much...
1419 $tempID = $this->_params
[0]['select_contact_id'];
1422 // force to ignore the authenticated user
1423 if ($tempID === '0' ||
$tempID === 0) {
1424 // we set the cid on the form so that this will be retained for the Confirm page
1425 // in the multi-page form & prevent us returning the $userID when this is called
1427 // we don't really need to set it when $tempID is set because the params have that stored
1428 $this->set('cid', 0);
1432 $userID = $this->getLoggedInUserContactID();
1434 if ($tempID == $userID) {
1438 //check if this is a checksum authentication
1439 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1440 if ($userChecksum) {
1441 //check for anonymous user.
1442 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1447 // check if user has permission, CRM-12062
1448 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1456 * Get the contact id of the logged in user
1458 function getLoggedInUserContactID() {
1459 // check if the user is logged in and has a contact ID
1460 $session = CRM_Core_Session
::singleton();
1461 return $session->get('userID');
1465 * add autoselector field -if user has permission to view contacts
1466 * If adding this to a form you also need to add to the tpl e.g
1468 * {if !empty($selectable)}
1469 * <div class="crm-summary-row">
1470 * <div class="crm-label">{$form.select_contact.label}</div>
1471 * <div class="crm-content">
1472 * {$form.select_contact.html}
1477 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1478 * @param array $autoCompleteField
1480 * @internal param array $field metadata of field to use as selector including
1483 * - url (for ajax lookup)
1485 * @todo add data attributes so we can deal with multiple instances on a form
1487 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1488 $autoCompleteField = array_merge(array(
1489 'id_field' => 'select_contact_id',
1490 'placeholder' => ts('Select someone else ...'),
1491 'show_hide' => TRUE,
1492 'api' => array('params' => array('contact_type' => 'Individual'))
1493 ), $autoCompleteField);
1495 if($this->canUseAjaxContactLookups()) {
1496 $this->assign('selectable', $autoCompleteField['id_field']);
1497 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1499 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1501 'form' => array('autocompletes' => $autoCompleteField),
1502 'ids' => array('profile' => $profiles),
1510 function canUseAjaxContactLookups() {
1511 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1512 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1518 * Add the options appropriate to cid = zero - ie. autocomplete
1520 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1521 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1522 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1523 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1525 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1526 $this->assign('nocid', TRUE);
1527 $profiles = array();
1528 if($this->_values
['custom_pre_id']) {
1529 $profiles[] = $this->_values
['custom_pre_id'];
1531 if($this->_values
['custom_post_id']) {
1532 $profiles[] = $this->_values
['custom_post_id'];
1534 if($onlinePaymentProcessorEnabled) {
1535 $profiles[] = 'billing';
1537 if(!empty($this->_values
)) {
1538 $this->addAutoSelector($profiles);
1543 * Set default values on form for given contact (or no contact defaults)
1545 * @param mixed $profile_id (can be id, or profile name)
1546 * @param integer $contactID
1550 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1552 $defaults = civicrm_api3('profile', 'getsingle', array(
1553 'profile_id' => (array) $profile_id,
1554 'contact_id' => $contactID,
1558 catch (Exception
$e) {
1559 // the try catch block gives us silent failure -not 100% sure this is a good idea
1560 // as silent failures are often worse than noisy ones
1566 * Sets form attribute
1569 function preventAjaxSubmit() {
1570 $this->setAttribute('data-no-ajax-submit', 'true');
1574 * Sets form attribute
1577 function allowAjaxSubmit() {
1578 $this->removeAttribute('data-no-ajax-submit');