Merge pull request #1315 from pradpnayak/CRM-12972
[civicrm-core.git] / CRM / Core / Form.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26*/
27
28/**
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
32 *
33 * @package CRM
34 * @copyright CiviCRM LLC (c) 2004-2013
35 * $Id$
36 *
37 */
38
39require_once 'HTML/QuickForm/Page.php';
40class CRM_Core_Form extends HTML_QuickForm_Page {
41
42 /**
43 * The state object that this form belongs to
44 * @var object
45 */
46 protected $_state;
47
48 /**
49 * The name of this form
50 * @var string
51 */
52 protected $_name;
53
54 /**
55 * The title of this form
56 * @var string
57 */
58 protected $_title = NULL;
59
60 /**
61 * The options passed into this form
62 * @var mixed
63 */
64 protected $_options = NULL;
65
66 /**
67 * The mode of operation for this form
68 * @var int
69 */
70 protected $_action;
71
72 /**
73 * the renderer used for this form
74 *
75 * @var object
76 */
77 protected $_renderer;
78
5d86176b 79 /**
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
82 *
83 * @var array
84 *
85 * e.g on a form declare $_dateFields = array(
86 * 'receive_date' => array('default' => 'now'),
87 * );
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
91 */
92 protected $_dateFields = array();
93
6a488035
TO
94 /**
95 * cache the smarty template for efficiency reasons
96 *
97 * @var CRM_Core_Smarty
98 */
99 static protected $_template;
100
101 /**
102 * constants for attributes for various form elements
103 * attempt to standardize on the number of variations that we
104 * use of the below form elements
105 *
106 * @var const string
107 */
108 CONST ATTR_SPACING = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
109
110 /**
111 * All checkboxes are defined with a common prefix. This allows us to
112 * have the same javascript to check / clear all the checkboxes etc
113 * If u have multiple groups of checkboxes, you will need to give them different
114 * ids to avoid potential name collision
115 *
116 * @var const string / int
117 */
118 CONST CB_PREFIX = 'mark_x_', CB_PREFIY = 'mark_y_', CB_PREFIZ = 'mark_z_', CB_PREFIX_LEN = 7;
119
120 /**
121 * Constructor for the basic form page
122 *
123 * We should not use QuickForm directly. This class provides a lot
124 * of default convenient functions, rules and buttons
125 *
126 * @param object $state State associated with this form
127 * @param enum $action The mode the form is operating in (None/Create/View/Update/Delete)
128 * @param string $method The type of http method used (GET/POST)
129 * @param string $name The name of the form if different from class name
130 *
131 * @return object
132 * @access public
133 */
134 function __construct(
135 $state = NULL,
136 $action = CRM_Core_Action::NONE,
137 $method = 'post',
138 $name = NULL
139 ) {
140
141 if ($name) {
142 $this->_name = $name;
143 }
144 else {
145 $this->_name = CRM_Utils_String::getClassName(CRM_Utils_System::getClassName($this));
146 }
147
148 $this->HTML_QuickForm_Page($this->_name, $method);
149
150 $this->_state =& $state;
151 if ($this->_state) {
152 $this->_state->setName($this->_name);
153 }
154 $this->_action = (int) $action;
155
156 $this->registerRules();
157
158 // let the constructor initialize this, should happen only once
159 if (!isset(self::$_template)) {
160 self::$_template = CRM_Core_Smarty::singleton();
161 }
162 }
163
164 static function generateID() {
165 }
166
167 /**
168 * register all the standard rules that most forms potentially use
169 *
170 * @return void
171 * @access private
172 *
173 */
174 function registerRules() {
175 static $rules = array(
176 'title', 'longTitle', 'variable', 'qfVariable',
177 'phone', 'integer', 'query',
178 'url', 'wikiURL',
179 'domain', 'numberOfDigit',
180 'date', 'currentDate',
181 'asciiFile', 'htmlFile', 'utf8File',
182 'objectExists', 'optionExists', 'postalCode', 'money', 'positiveInteger',
183 'xssString', 'fileExists', 'autocomplete', 'validContact',
184 );
185
186 foreach ($rules as $rule) {
187 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
188 }
189 }
190
191 /**
192 * Simple easy to use wrapper around addElement. Deal with
193 * simple validation rules
194 *
195 * @param string type of html element to be added
196 * @param string name of the html element
197 * @param string display label for the html element
198 * @param string attributes used for this element.
199 * These are not default values
200 * @param bool is this a required field
201 *
202 * @return object html element, could be an error object
203 * @access public
204 *
205 */
206 function &add($type, $name, $label = '',
207 $attributes = '', $required = FALSE, $javascript = NULL
208 ) {
209 $element = $this->addElement($type, $name, $label, $attributes, $javascript);
210 if (HTML_QuickForm::isError($element)) {
211 CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
212 }
213
214 if ($required) {
215 if ($type == 'file') {
216 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
217 }
218 else {
219 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
220 }
221 if (HTML_QuickForm::isError($error)) {
222 CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
223 }
224 }
225
226 return $element;
227 }
228
229 /**
230 * This function is called before buildForm. Any pre-processing that
231 * needs to be done for buildForm should be done here
232 *
233 * This is a virtual function and should be redefined if needed
234 *
235 * @access public
236 *
237 * @return void
238 *
239 */
240 function preProcess() {}
241
242 /**
243 * This function is called after the form is validated. Any
244 * processing of form state etc should be done in this function.
245 * Typically all processing associated with a form should be done
246 * here and relevant state should be stored in the session
247 *
248 * This is a virtual function and should be redefined if needed
249 *
250 * @access public
251 *
252 * @return void
253 *
254 */
255 function postProcess() {}
256
257 /**
258 * This function is just a wrapper, so that we can call all the hook functions
259 */
260 function mainProcess() {
261 $this->postProcess();
262
263 $this->postProcessHook();
264 }
265
266 /**
267 * The postProcess hook is typically called by the framework
268 * However in a few cases, the form exits or redirects early in which
269 * case it needs to call this function so other modules can do the needful
270 * Calling this function directly should be avoided if possible. In general a
271 * better way is to do setUserContext so the framework does the redirect
272 *
273 */
274 function postProcessHook() {
275 CRM_Utils_Hook::postProcess(get_class($this), $this);
276 }
277
278 /**
279 * This virtual function is used to build the form. It replaces the
280 * buildForm associated with QuickForm_Page. This allows us to put
281 * preProcess in front of the actual form building routine
282 *
283 * @access public
284 *
285 * @return void
286 *
287 */
288 function buildQuickForm() {}
289
290 /**
291 * This virtual function is used to set the default values of
292 * various form elements
293 *
294 * access public
295 *
296 * @return array reference to the array of default values
297 *
298 */
299 function setDefaultValues() {}
300
301 /**
302 * This is a virtual function that adds group and global rules to
303 * the form. Keeping it distinct from the form to keep code small
304 * and localized in the form building code
305 *
306 * @access public
307 *
308 * @return void
309 *
310 */
311 function addRules() {}
312
313 function validate() {
314 $error = parent::validate();
315
316 $hookErrors = CRM_Utils_Hook::validate(
317 get_class($this),
318 $this->_submitValues,
319 $this->_submitFiles,
320 $this
321 );
322
323 if (!is_array($hookErrors)) {
324 $hookErrors = array();
325 }
326
327 CRM_Utils_Hook::validateForm(
328 get_class($this),
329 $this->_submitValues,
330 $this->_submitFiles,
331 $this,
332 $hookErrors
333 );
334
335 if (!empty($hookErrors)) {
336 $this->_errors += $hookErrors;
337 }
338
339 return (0 == count($this->_errors));
340 }
341
342 /**
343 * Core function that builds the form. We redefine this function
344 * here and expect all CRM forms to build their form in the function
345 * buildQuickForm.
346 *
347 */
348 function buildForm() {
349 $this->_formBuilt = TRUE;
350
351 $this->preProcess();
352
353 $this->assign('translatePermission', CRM_Core_Permission::check('translate CiviCRM'));
354
355 if (
356 $this->controller->_key &&
357 $this->controller->_generateQFKey
358 ) {
359 $this->addElement('hidden', 'qfKey', $this->controller->_key);
360 $this->assign('qfKey', $this->controller->_key);
361 }
362
363
364 $this->buildQuickForm();
365
366 $defaults = $this->setDefaultValues();
367 unset($defaults['qfKey']);
368
369 if (!empty($defaults)) {
370 $this->setDefaults($defaults);
371 }
372
373 // call the form hook
374 // also call the hook function so any modules can set thier own custom defaults
375 // the user can do both the form and set default values with this hook
376 CRM_Utils_Hook::buildForm(get_class($this), $this);
377
378 $this->addRules();
379 }
380
381 /**
382 * Add default Next / Back buttons
383 *
384 * @param array array of associative arrays in the order in which the buttons should be
385 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
386 * The base form class will define a bunch of static arrays for commonly used
387 * formats
388 *
389 * @return void
390 *
391 * @access public
392 *
393 */
394 function addButtons($params) {
395 $prevnext = array();
396 $spacing = array();
397 foreach ($params as $button) {
398 $js = CRM_Utils_Array::value('js', $button);
399 $isDefault = CRM_Utils_Array::value('isDefault', $button, FALSE);
400 if ($isDefault) {
401 $attrs = array('class' => 'form-submit default');
402 }
403 else {
404 $attrs = array('class' => 'form-submit');
405 }
406
407 if ($js) {
408 $attrs = array_merge($js, $attrs);
409 }
410
411 if ($button['type'] === 'reset') {
412 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
413 }
414 else {
415 if (CRM_Utils_Array::value('subName', $button)) {
416 $buttonName = $this->getButtonName($button['type'], $button['subName']);
417 }
418 else {
419 $buttonName = $this->getButtonName($button['type']);
420 }
421
422 if (in_array($button['type'], array(
423 'next', 'upload')) && $button['name'] === 'Save') {
424 $attrs = array_merge($attrs, (array('accesskey' => 'S')));
425 }
426 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
427 }
428 if (CRM_Utils_Array::value('isDefault', $button)) {
429 $this->setDefaultAction($button['type']);
430 }
431
432 // if button type is upload, set the enctype
433 if ($button['type'] == 'upload') {
434 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
435 $this->setMaxFileSize();
436 }
437
438 // hack - addGroup uses an array to express variable spacing, read from the last element
439 $spacing[] = CRM_Utils_Array::value('spacing', $button, self::ATTR_SPACING);
440 }
441 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
442 }
443
444 /**
445 * getter function for Name
446 *
447 * @return string
448 * @access public
449 */
450 function getName() {
451 return $this->_name;
452 }
453
454 /**
455 * getter function for State
456 *
457 * @return object
458 * @access public
459 */
460 function &getState() {
461 return $this->_state;
462 }
463
464 /**
465 * getter function for StateType
466 *
467 * @return int
468 * @access public
469 */
470 function getStateType() {
471 return $this->_state->getType();
472 }
473
474 /**
475 * getter function for title. Should be over-ridden by derived class
476 *
477 * @return string
478 * @access public
479 */
480 function getTitle() {
481 return $this->_title ? $this->_title : ts('ERROR: Title is not Set');
482 }
483
484 /**
485 * setter function for title.
486 *
487 * @param string $title the title of the form
488 *
489 * @return void
490 * @access public
491 */
492 function setTitle($title) {
493 $this->_title = $title;
494 }
495
496 /**
497 * Setter function for options
498 *
499 * @param mixed
500 *
501 * @return void
502 * @access public
503 */
504 function setOptions($options) {
505 $this->_options = $options;
506 }
507
508 /**
509 * getter function for link.
510 *
511 * @return string
512 * @access public
513 */
514 function getLink() {
515 $config = CRM_Core_Config::singleton();
516 return CRM_Utils_System::url($_GET[$config->userFrameworkURLVar],
517 '_qf_' . $this->_name . '_display=true'
518 );
519 }
520
521 /**
522 * boolean function to determine if this is a one form page
523 *
524 * @return boolean
525 * @access public
526 */
527 function isSimpleForm() {
528 return $this->_state->getType() & (CRM_Core_State::START | CRM_Core_State::FINISH);
529 }
530
531 /**
532 * getter function for Form Action
533 *
534 * @return string
535 * @access public
536 */
537 function getFormAction() {
538 return $this->_attributes['action'];
539 }
540
541 /**
542 * setter function for Form Action
543 *
544 * @param string
545 *
546 * @return void
547 * @access public
548 */
549 function setFormAction($action) {
550 $this->_attributes['action'] = $action;
551 }
552
553 /**
554 * render form and return contents
555 *
556 * @return string
557 * @access public
558 */
559 function toSmarty() {
560 $renderer = $this->getRenderer();
561 $this->accept($renderer);
562 $content = $renderer->toArray();
563 $content['formName'] = $this->getName();
564 return $content;
565 }
566
567 /**
568 * getter function for renderer. If renderer is not set
569 * create one and initialize it
570 *
571 * @return object
572 * @access public
573 */
574 function &getRenderer() {
575 if (!isset($this->_renderer)) {
576 $this->_renderer = CRM_Core_Form_Renderer::singleton();
577 }
578 return $this->_renderer;
579 }
580
581 /**
582 * Use the form name to create the tpl file name
583 *
584 * @return string
585 * @access public
586 */
587 function getTemplateFileName() {
588 $ext = CRM_Extension_System::singleton()->getMapper();
589 if ($ext->isExtensionClass(CRM_Utils_System::getClassName($this))) {
590 $filename = $ext->getTemplateName(CRM_Utils_System::getClassName($this));
591 $tplname = $ext->getTemplatePath(CRM_Utils_System::getClassName($this)) . DIRECTORY_SEPARATOR . $filename;
592 }
593 else {
594 $tplname = str_replace('_',
595 DIRECTORY_SEPARATOR,
596 CRM_Utils_System::getClassName($this)
597 ) . '.tpl';
598 }
599 return $tplname;
600 }
601
602 /**
603 * Default extra tpl file basically just replaces .tpl with .extra.tpl
604 * i.e. we dont override
605 *
606 * @return string
607 * @access public
608 */
609 function overrideExtraTemplateFileName() {
610 return NULL;
611 }
612
613 /**
614 * Error reporting mechanism
615 *
616 * @param string $message Error Message
617 * @param int $code Error Code
618 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
619 *
620 * @return void
621 * @access public
622 */
623 function error($message, $code = NULL, $dao = NULL) {
624 if ($dao) {
625 $dao->query('ROLLBACK');
626 }
627
628 $error = CRM_Core_Error::singleton();
629
630 $error->push($code, $message);
631 }
632
633 /**
634 * Store the variable with the value in the form scope
635 *
636 * @param string name : name of the variable
637 * @param mixed value : value of the variable
638 *
639 * @access public
640 *
641 * @return void
642 *
643 */
644 function set($name, $value) {
645 $this->controller->set($name, $value);
646 }
647
648 /**
649 * Get the variable from the form scope
650 *
651 * @param string name : name of the variable
652 *
653 * @access public
654 *
655 * @return mixed
656 *
657 */
658 function get($name) {
659 return $this->controller->get($name);
660 }
661
662 /**
663 * getter for action
664 *
665 * @return int
666 * @access public
667 */
668 function getAction() {
669 return $this->_action;
670 }
671
672 /**
673 * setter for action
674 *
675 * @param int $action the mode we want to set the form
676 *
677 * @return void
678 * @access public
679 */
680 function setAction($action) {
681 $this->_action = $action;
682 }
683
684 /**
685 * assign value to name in template
686 *
687 * @param array|string $name name of variable
688 * @param mixed $value value of varaible
689 *
690 * @return void
691 * @access public
692 */
693 function assign($var, $value = NULL) {
694 self::$_template->assign($var, $value);
695 }
696
697 /**
698 * assign value to name in template by reference
699 *
700 * @param array|string $name name of variable
701 * @param mixed $value value of varaible
702 *
703 * @return void
704 * @access public
705 */
706 function assign_by_ref($var, &$value) {
707 self::$_template->assign_by_ref($var, $value);
708 }
709
710 function &addRadio($name, $title, &$values, $attributes = NULL, $separator = NULL, $required = FALSE) {
711 $options = array();
712 if (empty($attributes)) {
713 $attributes = array('id_suffix' => $name);
714 }
715 else {
716 $attributes = array_merge($attributes, array('id_suffix' => $name));
717 }
718 foreach ($values as $key => $var) {
719 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
720 }
721 $group = $this->addGroup($options, $name, $title, $separator);
722 if ($required) {
723 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
724 }
725 return $group;
726 }
727
728 function addYesNo($id, $title, $dontKnow = NULL, $required = NULL, $attribute = NULL) {
729 if (empty($attribute)) {
730 $attribute = array('id_suffix' => $id);
731 }
732 else {
733 $attribute = array_merge($attribute, array('id_suffix' => $id));
734 }
735 $choice = array();
736 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attribute);
737 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attribute);
738 if ($dontKnow) {
739 $choice[] = $this->createElement('radio', NULL, '22', ts("Don't Know"), '2', $attribute);
740 }
741 $this->addGroup($choice, $id, $title);
742
743 if ($required) {
744 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
745 }
746 }
747
748 function addCheckBox($id, $title, $values, $other = NULL,
749 $attributes = NULL, $required = NULL,
750 $javascriptMethod = NULL,
751 $separator = '<br />', $flipValues = FALSE
752 ) {
753 $options = array();
754
755 if ($javascriptMethod) {
756 foreach ($values as $key => $var) {
757 if (!$flipValues) {
758 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
759 }
760 else {
761 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
762 }
763 }
764 }
765 else {
766 foreach ($values as $key => $var) {
767 if (!$flipValues) {
768 $options[] = $this->createElement('checkbox', $var, NULL, $key);
769 }
770 else {
771 $options[] = $this->createElement('checkbox', $key, NULL, $var);
772 }
773 }
774 }
775
776 $this->addGroup($options, $id, $title, $separator);
777
778 if ($other) {
779 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
780 }
781
782 if ($required) {
783 $this->addRule($id,
784 ts('%1 is a required field.', array(1 => $title)),
785 'required'
786 );
787 }
788 }
789
790 function resetValues() {
791 $data = $this->controller->container();
792 $data['values'][$this->_name] = array();
793 }
794
795 /**
796 * simple shell that derived classes can call to add buttons to
797 * the form with a customized title for the main Submit
798 *
799 * @param string $title title of the main button
800 * @param string $type button type for the form after processing
801 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
802 *
803 * @return void
804 * @access public
805 */
806 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
807 $buttons = array();
808 if ($backType != NULL) {
809 $buttons[] = array(
810 'type' => $backType,
811 'name' => ts('Previous'),
812 );
813 }
814 if ($nextType != NULL) {
815 $nextButton = array(
816 'type' => $nextType,
817 'name' => $title,
818 'isDefault' => TRUE,
819 );
820 if ($submitOnce) {
821 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
822 }
823 $buttons[] = $nextButton;
824 }
825 $this->addButtons($buttons);
826 }
827
828 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
829 if ($displayTime) {
830 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
831 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
832 } else {
833 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
834 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
835 }
836 }
837
838 function addSelect($name, $label, $prefix = NULL, $required = NULL, $extra = NULL, $select = '- select -') {
839 if ($prefix) {
840 $this->addElement('select', $name . '_id' . $prefix, $label,
841 array(
842 '' => $select) + CRM_Core_OptionGroup::values($name), $extra
843 );
844 if ($required) {
845 $this->addRule($name . '_id' . $prefix, ts('Please select %1', array(1 => $label)), 'required');
846 }
847 }
848 else {
849 $this->addElement('select', $name . '_id', $label,
850 array(
851 '' => $select) + CRM_Core_OptionGroup::values($name), $extra
852 );
853 if ($required) {
854 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
855 }
856 }
857 }
858
859 /**
860 * Add a widget for selecting/editing/creating/copying a profile form
861 *
862 * @param string $name HTML form-element name
863 * @param string $label Printable label
864 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
865 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
866 * @param array $entities
867 */
868 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
869 // Output widget
870 // FIXME: Instead of adhoc serialization, use a single json_encode()
871 CRM_UF_Page_ProfileEditor::registerProfileScripts();
872 CRM_UF_Page_ProfileEditor::registerSchemas(CRM_Utils_Array::collect('entity_type', $entities));
873 $this->add('text', $name, $label, array(
874 'class' => 'crm-profile-selector',
875 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
876 'data-group-type' => CRM_Core_BAO_UFGroup::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
877 'data-entities' => json_encode($entities),
878 ));
879 }
880
881 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
882 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
883 // 2. Based on the option, initialise proper editor
884 $editorID = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
885 'editor_id'
886 );
887 $editor = strtolower(CRM_Utils_Array::value($editorID,
cbf48754 888 CRM_Core_OptionGroup::values('wysiwyg_editor')
6a488035
TO
889 ));
890 if (!$editor || $forceTextarea) {
891 $editor = 'textarea';
892 }
893 if ($editor == 'joomla default editor') {
894 $editor = 'joomlaeditor';
895 }
896
897 if ($editor == 'drupal default editor') {
898 $editor = 'drupalwysiwyg';
899 }
900
901 //lets add the editor as a attribute
902 $attributes['editor'] = $editor;
903
904 $this->addElement($editor, $name, $label, $attributes);
905 $this->assign('editor', $editor);
906
907 // include wysiwyg editor js files
908 $includeWysiwygEditor = FALSE;
909 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
910 if (!$includeWysiwygEditor) {
911 $includeWysiwygEditor = TRUE;
912 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
913 }
914
915 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
916 }
917
918 function addCountry($id, $title, $required = NULL, $extra = NULL) {
919 $this->addElement('select', $id, $title,
920 array(
921 '' => ts('- select -')) + CRM_Core_PseudoConstant::country(), $extra
922 );
923 if ($required) {
924 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
925 }
926 }
927
928 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
929
930 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
931
932 if ($required) {
933 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
934 }
935 }
936
937 function buildAddressBlock($locationId, $title, $phone,
938 $alternatePhone = NULL, $addressRequired = NULL,
939 $phoneRequired = NULL, $altPhoneRequired = NULL,
940 $locationName = NULL
941 ) {
942 if (!$locationName) {
943 $locationName = "location";
944 }
945
946 $config = CRM_Core_Config::singleton();
947 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_Address');
948
949 $location[$locationId]['address']['street_address'] = $this->addElement('text', "{$locationName}[$locationId][address][street_address]", $title,
950 $attributes['street_address']
951 );
952 if ($addressRequired) {
953 $this->addRule("{$locationName}[$locationId][address][street_address]", ts("Please enter the Street Address for %1.", array(1 => $title)), 'required');
954 }
955
c29131ec 956 $location[$locationId]['address']['supplemental_address_1'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_1]", ts('Supplemental Address 1'),
6a488035
TO
957 $attributes['supplemental_address_1']
958 );
c29131ec 959 $location[$locationId]['address']['supplemental_address_2'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_2]", ts('Supplemental Address 2'),
6a488035
TO
960 $attributes['supplemental_address_2']
961 );
962
963 $location[$locationId]['address']['city'] = $this->addElement('text', "{$locationName}[$locationId][address][city]", ts('City'),
964 $attributes['city']
965 );
966 if ($addressRequired) {
967 $this->addRule("{$locationName}[$locationId][address][city]", ts("Please enter the City for %1.", array(1 => $title)), 'required');
968 }
969
970 $location[$locationId]['address']['postal_code'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code]", ts('Zip / Postal Code'),
971 $attributes['postal_code']
972 );
973 if ($addressRequired) {
974 $this->addRule("{$locationName}[$locationId][address][postal_code]", ts("Please enter the Zip/Postal Code for %1.", array(1 => $title)), 'required');
975 }
976
977 $location[$locationId]['address']['postal_code_suffix'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code_suffix]", ts('Add-on Code'),
978 array('size' => 4, 'maxlength' => 12)
979 );
980 $this->addRule("{$locationName}[$locationId][address][postal_code_suffix]", ts('Zip-Plus not valid.'), 'positiveInteger');
981
982 if ($config->includeCounty) {
983 $location[$locationId]['address']['county_id'] = $this->addElement('select', "{$locationName}[$locationId][address][county_id]", ts('County'),
984 array('' => ts('- select -')) + CRM_Core_PseudoConstant::county()
985 );
986 }
987
988 $location[$locationId]['address']['state_province_id'] = $this->addElement('select', "{$locationName}[$locationId][address][state_province_id]", ts('State / Province'),
989 array('' => ts('- select -')) + CRM_Core_PseudoConstant::stateProvince()
990 );
991
992 $location[$locationId]['address']['country_id'] = $this->addElement('select', "{$locationName}[$locationId][address][country_id]", ts('Country'),
993 array('' => ts('- select -')) + CRM_Core_PseudoConstant::country()
994 );
995 if ($addressRequired) {
996 $this->addRule("{$locationName}[$locationId][address][country_id]", ts("Please select the Country for %1.", array(1 => $title)), 'required');
997 }
998
999
1000 if ($phone) {
1001 $location[$locationId]['phone'][1]['phone'] = $this->addElement('text',
1002 "{$locationName}[$locationId][phone][1][phone]",
1003 $phone,
1004 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
1005 'phone'
1006 )
1007 );
1008 if ($phoneRequired) {
1009 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a value for %1', array(1 => $phone)), 'required');
1010 }
1011 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a valid number for %1', array(1 => $phone)), 'phone');
1012 }
1013
1014 if ($alternatePhone) {
1015 $location[$locationId]['phone'][2]['phone'] = $this->addElement('text',
1016 "{$locationName}[$locationId][phone][2][phone]",
1017 $alternatePhone,
1018 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
1019 'phone'
1020 )
1021 );
1022 if ($alternatePhoneRequired) {
1023 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a value for %1', array(1 => $alternatePhone)), 'required');
1024 }
1025 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a valid number for %1', array(1 => $alternatePhone)), 'phone');
1026 }
1027 }
1028
1029 public function getRootTitle() {
1030 return NULL;
1031 }
1032
1033 public function getCompleteTitle() {
1034 return $this->getRootTitle() . $this->getTitle();
1035 }
1036
1037 static function &getTemplate() {
1038 return self::$_template;
1039 }
1040
1041 function addUploadElement($elementName) {
1042 $uploadNames = $this->get('uploadNames');
1043 if (!$uploadNames) {
1044 $uploadNames = array();
1045 }
1046 if (is_array($elementName)) {
1047 foreach ($elementName as $name) {
1048 if (!in_array($name, $uploadNames)) {
1049 $uploadNames[] = $name;
1050 }
1051 }
1052 }
1053 else {
1054 if (!in_array($elementName, $uploadNames)) {
1055 $uploadNames[] = $elementName;
1056 }
1057 }
1058 $this->set('uploadNames', $uploadNames);
1059
1060 $config = CRM_Core_Config::singleton();
1061 if (!empty($uploadNames)) {
1062 $this->controller->addUploadAction($config->customFileUploadDir, $uploadNames);
1063 }
1064 }
1065
1066 function buttonType() {
1067 $uploadNames = $this->get('uploadNames');
1068 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ? 'upload' : 'next';
1069 $this->assign('buttonType', $buttonType);
1070 return $buttonType;
1071 }
1072
1073 function getVar($name) {
1074 return isset($this->$name) ? $this->$name : NULL;
1075 }
1076
1077 function setVar($name, $value) {
1078 $this->$name = $value;
1079 }
1080
1081 /**
1082 * Function to add date
1083 * @param string $name name of the element
1084 * @param string $label label of the element
1085 * @param array $attributes key / value pair
1086 *
1087 // if you need time
1088 * $attributes = array ( 'addTime' => true,
1089 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1090 * );
1091 * @param boolean $required true if required
1092 *
1093 */
1094 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1095 if (CRM_Utils_Array::value('formatType', $attributes)) {
1096 // get actual format
1097 $params = array('name' => $attributes['formatType']);
1098 $values = array();
1099
1100 // cache date information
1101 static $dateFormat;
1102 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1103 if (!CRM_Utils_Array::value($key, $dateFormat)) {
1104 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1105 $dateFormat[$key] = $values;
1106 }
1107 else {
1108 $values = $dateFormat[$key];
1109 }
1110
1111 if ($values['date_format']) {
1112 $attributes['format'] = $values['date_format'];
1113 }
1114
1115 if (CRM_Utils_Array::value('time_format', $values)) {
1116 $attributes['timeFormat'] = $values['time_format'];
1117 }
1118 $attributes['startOffset'] = $values['start'];
1119 $attributes['endOffset'] = $values['end'];
1120 }
1121
1122 $config = CRM_Core_Config::singleton();
1123 if (!CRM_Utils_Array::value('format', $attributes)) {
1124 $attributes['format'] = $config->dateInputFormat;
1125 }
1126
1127 if (!isset($attributes['startOffset'])) {
1128 $attributes['startOffset'] = 10;
1129 }
1130
1131 if (!isset($attributes['endOffset'])) {
1132 $attributes['endOffset'] = 10;
1133 }
1134
1135 $this->add('text', $name, $label, $attributes);
1136
1137 if (CRM_Utils_Array::value('addTime', $attributes) ||
1138 CRM_Utils_Array::value('timeFormat', $attributes)
1139 ) {
1140
1141 if (!isset($attributes['timeFormat'])) {
1142 $timeFormat = $config->timeInputFormat;
1143 }
1144 else {
1145 $timeFormat = $attributes['timeFormat'];
1146 }
1147
1148 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1149 if ($timeFormat) {
1150 $show24Hours = TRUE;
1151 if ($timeFormat == 1) {
1152 $show24Hours = FALSE;
1153 }
1154
1155 //CRM-6664 -we are having time element name
1156 //in either flat string or an array format.
1157 $elementName = $name . '_time';
1158 if (substr($name, -1) == ']') {
1159 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1160 }
1161
1162 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1163 }
1164 }
1165
1166 if ($required) {
1167 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1168 if (CRM_Utils_Array::value('addTime', $attributes) && CRM_Utils_Array::value('addTimeRequired', $attributes)) {
1169 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1170 }
1171 }
1172 }
1173
1174 /**
1175 * Function that will add date and time
1176 */
1177 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1178 $addTime = array('addTime' => TRUE);
1179 if (is_array($attributes)) {
1180 $attributes = array_merge($attributes, $addTime);
1181 }
1182 else {
1183 $attributes = $addTime;
1184 }
1185
1186 $this->addDate($name, $label, $required, $attributes);
1187 }
1188
1189 /**
1190 * add a currency and money element to the form
1191 */
1192 function addMoney($name,
1193 $label,
1194 $required = FALSE,
1195 $attributes = NULL,
1196 $addCurrency = TRUE,
1197 $currencyName = 'currency',
1198 $defaultCurrency = NULL,
1199 $freezeCurrency = FALSE
1200 ) {
1201 $element = $this->add('text', $name, $label, $attributes, $required);
1202 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1203
1204 if ($addCurrency) {
1205 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1206 }
1207
1208 return $element;
1209 }
1210
1211 /**
1212 * add currency element to the form
1213 */
1214 function addCurrency($name = 'currency',
1215 $label = NULL,
1216 $required = TRUE,
1217 $defaultCurrency = NULL,
1218 $freezeCurrency = FALSE
1219 ) {
1220 $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
1221 if (!$required) {
1222 $currencies = array(
1223 '' => ts('- select -')) + $currencies;
1224 }
1225 $ele = $this->add('select', $name, $label, $currencies, $required);
1226 if ($freezeCurrency) {
1227 $ele->freeze();
1228 }
1229 if (!$defaultCurrency) {
1230 $config = CRM_Core_Config::singleton();
1231 $defaultCurrency = $config->defaultCurrency;
1232 }
1233 $this->setDefaults(array($name => $defaultCurrency));
1234 }
1235
5d86176b 1236 /**
1237 * Convert all date fields within the params to mysql date ready for the
1238 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1239 * and if time is defined it is incorporated
1240 *
1241 * @param array $params input params from the form
1242 *
1243 * @todo it would probably be better to work on $this->_params than a passed array
1244 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1245 * handling from BAO
1246 */
1247 function convertDateFieldsToMySQL(&$params){
1248 foreach ($this->_dateFields as $fieldName => $specs){
1249 if(!empty($params[$fieldName])){
1250 $params[$fieldName] = CRM_Utils_Date::isoToMysql(
1251 CRM_Utils_Date::processDate(
1252 $params[$fieldName],
1253 CRM_Utils_Array::value("{$fieldName}_time", $params), TRUE)
1254 );
1255 }
1256 else{
1257 if(isset($specs['default'])){
1258 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1259 }
1260 }
1261 }
1262 }
1263
6a488035
TO
1264 function removeFileRequiredRules($elementName) {
1265 $this->_required = array_diff($this->_required, array($elementName));
1266 if (isset($this->_rules[$elementName])) {
1267 foreach ($this->_rules[$elementName] as $index => $ruleInfo) {
1268 if ($ruleInfo['type'] == 'uploadedfile') {
1269 unset($this->_rules[$elementName][$index]);
1270 }
1271 }
1272 if (empty($this->_rules[$elementName])) {
1273 unset($this->_rules[$elementName]);
1274 }
1275 }
1276 }
1277
1278 /**
1279 * Function that can be defined in Form to override or
1280 * perform specific action on cancel action
1281 *
1282 * @access public
1283 */
1284 function cancelAction() {}
7cb3d4f0
CW
1285
1286 /**
1287 * Helper function to verify that required fields have been filled
1288 * Typically called within the scope of a FormRule function
1289 */
1290 static function validateMandatoryFields($fields, $values, &$errors) {
1291 foreach ($fields as $name => $fld) {
1292 if (!empty($fld['is_required']) && CRM_Utils_System::isNull(CRM_Utils_Array::value($name, $values))) {
1293 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1294 }
1295 }
1296 }
6a488035
TO
1297}
1298