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 $nextType
887 * @param string $backType
888 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
890 * @internal param string $type button type for the form after processing
894 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
896 if ($backType != NULL) {
899 'name' => ts('Previous'),
902 if ($nextType != NULL) {
909 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
911 $buttons[] = $nextButton;
913 $this->addButtons($buttons);
916 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
918 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
919 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
921 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
922 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
927 * Adds a select based on field metadata
928 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
929 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
930 * @param $name - field name to go on the form
931 * @param array $props - mix of html attributes and special properties, namely
932 * - entity (api entity name, can usually be inferred automatically from the form class)
933 * - field (field name - only needed if different from name used on the form)
934 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
935 * - placeholder - set to NULL to disable
937 * @param bool $required
938 * @throws CRM_Core_Exception
939 * @return HTML_QuickForm_Element
941 function addSelect($name, $props = array(), $required = FALSE) {
942 if (!isset($props['entity'])) {
943 $props['entity'] = CRM_Utils_Api
::getEntityName($this);
945 if (!isset($props['field'])) {
946 $props['field'] = strrpos($name, '[') ?
rtrim(substr($name, 1 +
strrpos($name, '[')), ']') : $name;
948 $info = civicrm_api3($props['entity'], 'getoptions', array(
949 'field' => $props['field'],
950 'options' => array('metadata' => array('fields'))
953 $options = $info['values'];
954 if (!array_key_exists('placeholder', $props)) {
955 $props['placeholder'] = $required ?
ts('- select -') : ts('- none -');
957 if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
958 $options = array('' => '') +
$options;
960 // Handle custom field
961 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
962 list(, $id) = explode('_', $name);
963 $label = isset($props['label']) ?
$props['label'] : CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
964 $gid = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
965 $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);
969 foreach($info['metadata']['fields'] as $uniqueName => $fieldSpec) {
971 $uniqueName === $props['field'] ||
972 CRM_Utils_Array
::value('name', $fieldSpec) === $props['field'] ||
973 in_array($props['field'], CRM_Utils_Array
::value('api.aliases', $fieldSpec, array()))
978 $label = isset($props['label']) ?
$props['label'] : $fieldSpec['title'];
979 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ?
$props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant
::getOptionEditUrl($fieldSpec);
981 $props['class'] = (isset($props['class']) ?
$props['class'] . ' ' : '') . "crm-select2";
982 $props['data-api-entity'] = $props['entity'];
983 $props['data-api-field'] = $props['field'];
984 CRM_Utils_Array
::remove($props, 'label', 'entity', 'field', 'option_url');
985 return $this->add('select', $name, $label, $options, $required, $props);
989 * Add a widget for selecting/editing/creating/copying a profile form
991 * @param string $name HTML form-element name
992 * @param string $label Printable label
993 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
994 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
995 * @param array $entities
997 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
999 // FIXME: Instead of adhoc serialization, use a single json_encode()
1000 CRM_UF_Page_ProfileEditor
::registerProfileScripts();
1001 CRM_UF_Page_ProfileEditor
::registerSchemas(CRM_Utils_Array
::collect('entity_type', $entities));
1002 $this->add('text', $name, $label, array(
1003 'class' => 'crm-profile-selector',
1004 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1005 'data-group-type' => CRM_Core_BAO_UFGroup
::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1006 'data-entities' => json_encode($entities),
1010 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1011 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1012 // 2. Based on the option, initialise proper editor
1013 $editorID = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
1016 $editor = strtolower(CRM_Utils_Array
::value($editorID,
1017 CRM_Core_OptionGroup
::values('wysiwyg_editor')
1019 if (!$editor ||
$forceTextarea) {
1020 $editor = 'textarea';
1022 if ($editor == 'joomla default editor') {
1023 $editor = 'joomlaeditor';
1026 if ($editor == 'drupal default editor') {
1027 $editor = 'drupalwysiwyg';
1030 //lets add the editor as a attribute
1031 $attributes['editor'] = $editor;
1033 $this->addElement($editor, $name, $label, $attributes);
1034 $this->assign('editor', $editor);
1036 // include wysiwyg editor js files
1037 // FIXME: This code does not make any sense
1038 $includeWysiwygEditor = FALSE;
1039 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1040 if (!$includeWysiwygEditor) {
1041 $includeWysiwygEditor = TRUE;
1042 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1045 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1048 function addCountry($id, $title, $required = NULL, $extra = NULL) {
1049 $this->addElement('select', $id, $title,
1051 '' => ts('- select -')) + CRM_Core_PseudoConstant
::country(), $extra
1054 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1058 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1060 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1063 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1067 public function getRootTitle() {
1071 public function getCompleteTitle() {
1072 return $this->getRootTitle() . $this->getTitle();
1075 static function &getTemplate() {
1076 return self
::$_template;
1079 function addUploadElement($elementName) {
1080 $uploadNames = $this->get('uploadNames');
1081 if (!$uploadNames) {
1082 $uploadNames = array();
1084 if (is_array($elementName)) {
1085 foreach ($elementName as $name) {
1086 if (!in_array($name, $uploadNames)) {
1087 $uploadNames[] = $name;
1092 if (!in_array($elementName, $uploadNames)) {
1093 $uploadNames[] = $elementName;
1096 $this->set('uploadNames', $uploadNames);
1098 $config = CRM_Core_Config
::singleton();
1099 if (!empty($uploadNames)) {
1100 $this->controller
->addUploadAction($config->customFileUploadDir
, $uploadNames);
1104 function buttonType() {
1105 $uploadNames = $this->get('uploadNames');
1106 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ?
'upload' : 'next';
1107 $this->assign('buttonType', $buttonType);
1111 function getVar($name) {
1112 return isset($this->$name) ?
$this->$name : NULL;
1115 function setVar($name, $value) {
1116 $this->$name = $value;
1120 * Function to add date
1121 * @param string $name name of the element
1122 * @param string $label label of the element
1123 * @param array $attributes key / value pair
1126 * $attributes = array ( 'addTime' => true,
1127 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1129 * @param boolean $required true if required
1132 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1133 if (!empty($attributes['formatType'])) {
1134 // get actual format
1135 $params = array('name' => $attributes['formatType']);
1138 // cache date information
1140 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1141 if (empty($dateFormat[$key])) {
1142 CRM_Core_DAO
::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1143 $dateFormat[$key] = $values;
1146 $values = $dateFormat[$key];
1149 if ($values['date_format']) {
1150 $attributes['format'] = $values['date_format'];
1153 if (!empty($values['time_format'])) {
1154 $attributes['timeFormat'] = $values['time_format'];
1156 $attributes['startOffset'] = $values['start'];
1157 $attributes['endOffset'] = $values['end'];
1160 $config = CRM_Core_Config
::singleton();
1161 if (empty($attributes['format'])) {
1162 $attributes['format'] = $config->dateInputFormat
;
1165 if (!isset($attributes['startOffset'])) {
1166 $attributes['startOffset'] = 10;
1169 if (!isset($attributes['endOffset'])) {
1170 $attributes['endOffset'] = 10;
1173 $this->add('text', $name, $label, $attributes);
1175 if (!empty($attributes['addTime']) ||
!empty($attributes['timeFormat'])) {
1177 if (!isset($attributes['timeFormat'])) {
1178 $timeFormat = $config->timeInputFormat
;
1181 $timeFormat = $attributes['timeFormat'];
1184 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1186 $show24Hours = TRUE;
1187 if ($timeFormat == 1) {
1188 $show24Hours = FALSE;
1191 //CRM-6664 -we are having time element name
1192 //in either flat string or an array format.
1193 $elementName = $name . '_time';
1194 if (substr($name, -1) == ']') {
1195 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1198 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1203 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1204 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1205 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1211 * Function that will add date and time
1213 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1214 $addTime = array('addTime' => TRUE);
1215 if (is_array($attributes)) {
1216 $attributes = array_merge($attributes, $addTime);
1219 $attributes = $addTime;
1222 $this->addDate($name, $label, $required, $attributes);
1226 * add a currency and money element to the form
1228 function addMoney($name,
1232 $addCurrency = TRUE,
1233 $currencyName = 'currency',
1234 $defaultCurrency = NULL,
1235 $freezeCurrency = FALSE
1237 $element = $this->add('text', $name, $label, $attributes, $required);
1238 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1241 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1248 * add currency element to the form
1250 function addCurrency($name = 'currency',
1253 $defaultCurrency = NULL,
1254 $freezeCurrency = FALSE
1256 $currencies = CRM_Core_OptionGroup
::values('currencies_enabled');
1257 $options = array('class' => 'crm-select2 eight');
1259 $currencies = array('' => '') +
$currencies;
1260 $options['placeholder'] = ts('- none -');
1262 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1263 if ($freezeCurrency) {
1266 if (!$defaultCurrency) {
1267 $config = CRM_Core_Config
::singleton();
1268 $defaultCurrency = $config->defaultCurrency
;
1270 $this->setDefaults(array($name => $defaultCurrency));
1274 * Create a single or multiple entity ref field
1275 * @param string $name
1276 * @param string $label
1277 * @param array $props mix of html and widget properties, including:
1278 * - select - params to give to select2 widget
1279 * - entity - defaults to contact
1280 * - create - can the user create a new entity on-the-fly?
1281 * Set to TRUE if entity is contact and you want the default profiles,
1282 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1283 * note that permissions are checked automatically
1284 * - api - array of settings for the getlist api wrapper
1285 * note that it accepts a 'params' setting which will be passed to the underlying api
1286 * - placeholder - string
1288 * - class, etc. - other html properties
1289 * @param bool $required
1292 * @return HTML_QuickForm_Element
1294 function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1295 require_once "api/api.php";
1296 $config = CRM_Core_Config
::singleton();
1297 // Default properties
1298 $props['api'] = CRM_Utils_Array
::value('api', $props, array());
1299 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array
::value('entity', $props, 'contact'));
1300 $props['class'] = ltrim(CRM_Utils_Array
::value('class', $props, '') . ' crm-form-entityref');
1302 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission
::check('edit all contacts') || CRM_Core_Permission
::check('add contacts'))) {
1303 unset($props['create']);
1306 $props['placeholder'] = CRM_Utils_Array
::value('placeholder', $props, $required ?
ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1308 $defaults = array();
1309 if (!empty($props['multiple'])) {
1310 $defaults['multiple'] = TRUE;
1312 $props['select'] = CRM_Utils_Array
::value('select', $props, array()) +
$defaults;
1314 $this->formatReferenceFieldAttributes($props);
1315 return $this->add('text', $name, $label, $props, $required);
1321 private function formatReferenceFieldAttributes(&$props) {
1322 $props['data-select-params'] = json_encode($props['select']);
1323 $props['data-api-params'] = $props['api'] ?
json_encode($props['api']) : NULL;
1324 $props['data-api-entity'] = $props['entity'];
1325 if (!empty($props['create'])) {
1326 $props['data-create-links'] = json_encode($props['create']);
1328 CRM_Utils_Array
::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1332 * Convert all date fields within the params to mysql date ready for the
1333 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1334 * and if time is defined it is incorporated
1336 * @param array $params input params from the form
1338 * @todo it would probably be better to work on $this->_params than a passed array
1339 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1342 function convertDateFieldsToMySQL(&$params){
1343 foreach ($this->_dateFields
as $fieldName => $specs){
1344 if(!empty($params[$fieldName])){
1345 $params[$fieldName] = CRM_Utils_Date
::isoToMysql(
1346 CRM_Utils_Date
::processDate(
1347 $params[$fieldName],
1348 CRM_Utils_Array
::value("{$fieldName}_time", $params), TRUE)
1352 if(isset($specs['default'])){
1353 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1359 function removeFileRequiredRules($elementName) {
1360 $this->_required
= array_diff($this->_required
, array($elementName));
1361 if (isset($this->_rules
[$elementName])) {
1362 foreach ($this->_rules
[$elementName] as $index => $ruleInfo) {
1363 if ($ruleInfo['type'] == 'uploadedfile') {
1364 unset($this->_rules
[$elementName][$index]);
1367 if (empty($this->_rules
[$elementName])) {
1368 unset($this->_rules
[$elementName]);
1374 * Function that can be defined in Form to override or
1375 * perform specific action on cancel action
1379 function cancelAction() {}
1382 * Helper function to verify that required fields have been filled
1383 * Typically called within the scope of a FormRule function
1385 static function validateMandatoryFields($fields, $values, &$errors) {
1386 foreach ($fields as $name => $fld) {
1387 if (!empty($fld['is_required']) && CRM_Utils_System
::isNull(CRM_Utils_Array
::value($name, $values))) {
1388 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1394 * Get contact if for a form object. Prioritise
1395 * - cid in URL if 0 (on behalf on someoneelse)
1396 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1397 * - logged in user id if it matches the one in the cid in the URL
1398 * - contact id validated from a checksum from a checksum
1399 * - cid from the url if the caller has ACL permission to view
1400 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1402 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1404 function getContactID() {
1405 $tempID = CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
1406 if(isset($this->_params
) && isset($this->_params
['select_contact_id'])) {
1407 $tempID = $this->_params
['select_contact_id'];
1409 if(isset($this->_params
, $this->_params
[0]) && !empty($this->_params
[0]['select_contact_id'])) {
1410 // event form stores as an indexed array, contribution form not so much...
1411 $tempID = $this->_params
[0]['select_contact_id'];
1414 // force to ignore the authenticated user
1415 if ($tempID === '0' ||
$tempID === 0) {
1416 // we set the cid on the form so that this will be retained for the Confirm page
1417 // in the multi-page form & prevent us returning the $userID when this is called
1419 // we don't really need to set it when $tempID is set because the params have that stored
1420 $this->set('cid', 0);
1424 $userID = $this->getLoggedInUserContactID();
1426 if ($tempID == $userID) {
1430 //check if this is a checksum authentication
1431 $userChecksum = CRM_Utils_Request
::retrieve('cs', 'String', $this);
1432 if ($userChecksum) {
1433 //check for anonymous user.
1434 $validUser = CRM_Contact_BAO_Contact_Utils
::validChecksum($tempID, $userChecksum);
1439 // check if user has permission, CRM-12062
1440 else if ($tempID && CRM_Contact_BAO_Contact_Permission
::allow($tempID)) {
1448 * Get the contact id of the logged in user
1450 function getLoggedInUserContactID() {
1451 // check if the user is logged in and has a contact ID
1452 $session = CRM_Core_Session
::singleton();
1453 return $session->get('userID');
1457 * add autoselector field -if user has permission to view contacts
1458 * If adding this to a form you also need to add to the tpl e.g
1460 * {if !empty($selectable)}
1461 * <div class="crm-summary-row">
1462 * <div class="crm-label">{$form.select_contact.label}</div>
1463 * <div class="crm-content">
1464 * {$form.select_contact.html}
1469 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1470 * @param array $autoCompleteField
1472 * @internal param array $field metadata of field to use as selector including
1475 * - url (for ajax lookup)
1477 * @todo add data attributes so we can deal with multiple instances on a form
1479 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1480 $autoCompleteField = array_merge(array(
1481 'id_field' => 'select_contact_id',
1482 'placeholder' => ts('Select someone else ...'),
1483 'show_hide' => TRUE,
1484 'api' => array('params' => array('contact_type' => 'Individual'))
1485 ), $autoCompleteField);
1487 if($this->canUseAjaxContactLookups()) {
1488 $this->assign('selectable', $autoCompleteField['id_field']);
1489 $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
1491 CRM_Core_Resources
::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
1493 'form' => array('autocompletes' => $autoCompleteField),
1494 'ids' => array('profile' => $profiles),
1502 function canUseAjaxContactLookups() {
1503 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1504 CRM_Core_Permission
::check(array(array('access AJAX API', 'access CiviCRM')))) {
1510 * Add the options appropriate to cid = zero - ie. autocomplete
1512 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1513 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1514 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1515 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1517 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1518 $this->assign('nocid', TRUE);
1519 $profiles = array();
1520 if($this->_values
['custom_pre_id']) {
1521 $profiles[] = $this->_values
['custom_pre_id'];
1523 if($this->_values
['custom_post_id']) {
1524 $profiles[] = $this->_values
['custom_post_id'];
1526 if($onlinePaymentProcessorEnabled) {
1527 $profiles[] = 'billing';
1529 if(!empty($this->_values
)) {
1530 $this->addAutoSelector($profiles);
1535 * Set default values on form for given contact (or no contact defaults)
1537 * @param mixed $profile_id (can be id, or profile name)
1538 * @param integer $contactID
1542 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1544 $defaults = civicrm_api3('profile', 'getsingle', array(
1545 'profile_id' => (array) $profile_id,
1546 'contact_id' => $contactID,
1550 catch (Exception
$e) {
1551 // the try catch block gives us silent failure -not 100% sure this is a good idea
1552 // as silent failures are often worse than noisy ones
1558 * Sets form attribute
1561 function preventAjaxSubmit() {
1562 $this->setAttribute('data-no-ajax-submit', 'true');
1566 * Sets form attribute
1569 function allowAjaxSubmit() {
1570 $this->removeAttribute('data-no-ajax-submit');