CRM-13179 move js file per feedback, wording tweaks
[civicrm-core.git] / CRM / Core / Form.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
232624b1 4 | CiviCRM version 4.4 |
6a488035
TO
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
8aac22c8 602 /**
603 * A wrapper for getTemplateFileName that includes calling the hook to
604 * prevent us from having to copy & paste the logic of calling the hook
605 */
606 function getHookedTemplateFileName() {
607 $pageTemplateFile = $this->getTemplateFileName();
608 CRM_Utils_Hook::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
609 return $pageTemplateFile;
610 }
611
6a488035
TO
612 /**
613 * Default extra tpl file basically just replaces .tpl with .extra.tpl
614 * i.e. we dont override
615 *
616 * @return string
617 * @access public
618 */
619 function overrideExtraTemplateFileName() {
620 return NULL;
621 }
622
623 /**
624 * Error reporting mechanism
625 *
626 * @param string $message Error Message
627 * @param int $code Error Code
628 * @param CRM_Core_DAO $dao A data access object on which we perform a rollback if non - empty
629 *
630 * @return void
631 * @access public
632 */
633 function error($message, $code = NULL, $dao = NULL) {
634 if ($dao) {
635 $dao->query('ROLLBACK');
636 }
637
638 $error = CRM_Core_Error::singleton();
639
640 $error->push($code, $message);
641 }
642
643 /**
644 * Store the variable with the value in the form scope
645 *
646 * @param string name : name of the variable
647 * @param mixed value : value of the variable
648 *
649 * @access public
650 *
651 * @return void
652 *
653 */
654 function set($name, $value) {
655 $this->controller->set($name, $value);
656 }
657
658 /**
659 * Get the variable from the form scope
660 *
661 * @param string name : name of the variable
662 *
663 * @access public
664 *
665 * @return mixed
666 *
667 */
668 function get($name) {
669 return $this->controller->get($name);
670 }
671
672 /**
673 * getter for action
674 *
675 * @return int
676 * @access public
677 */
678 function getAction() {
679 return $this->_action;
680 }
681
682 /**
683 * setter for action
684 *
685 * @param int $action the mode we want to set the form
686 *
687 * @return void
688 * @access public
689 */
690 function setAction($action) {
691 $this->_action = $action;
692 }
693
694 /**
695 * assign value to name in template
696 *
697 * @param array|string $name name of variable
698 * @param mixed $value value of varaible
699 *
700 * @return void
701 * @access public
702 */
703 function assign($var, $value = NULL) {
704 self::$_template->assign($var, $value);
705 }
706
707 /**
708 * assign value to name in template by reference
709 *
710 * @param array|string $name name of variable
711 * @param mixed $value value of varaible
712 *
713 * @return void
714 * @access public
715 */
716 function assign_by_ref($var, &$value) {
717 self::$_template->assign_by_ref($var, $value);
718 }
719
720 function &addRadio($name, $title, &$values, $attributes = NULL, $separator = NULL, $required = FALSE) {
721 $options = array();
722 if (empty($attributes)) {
723 $attributes = array('id_suffix' => $name);
724 }
725 else {
726 $attributes = array_merge($attributes, array('id_suffix' => $name));
727 }
728 foreach ($values as $key => $var) {
729 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
730 }
731 $group = $this->addGroup($options, $name, $title, $separator);
732 if ($required) {
733 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
734 }
735 return $group;
736 }
737
738 function addYesNo($id, $title, $dontKnow = NULL, $required = NULL, $attribute = NULL) {
739 if (empty($attribute)) {
740 $attribute = array('id_suffix' => $id);
741 }
742 else {
743 $attribute = array_merge($attribute, array('id_suffix' => $id));
744 }
745 $choice = array();
746 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attribute);
747 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attribute);
748 if ($dontKnow) {
749 $choice[] = $this->createElement('radio', NULL, '22', ts("Don't Know"), '2', $attribute);
750 }
751 $this->addGroup($choice, $id, $title);
752
753 if ($required) {
754 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
755 }
756 }
757
758 function addCheckBox($id, $title, $values, $other = NULL,
759 $attributes = NULL, $required = NULL,
760 $javascriptMethod = NULL,
761 $separator = '<br />', $flipValues = FALSE
762 ) {
763 $options = array();
764
765 if ($javascriptMethod) {
766 foreach ($values as $key => $var) {
767 if (!$flipValues) {
768 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
769 }
770 else {
771 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
772 }
773 }
774 }
775 else {
776 foreach ($values as $key => $var) {
777 if (!$flipValues) {
778 $options[] = $this->createElement('checkbox', $var, NULL, $key);
779 }
780 else {
781 $options[] = $this->createElement('checkbox', $key, NULL, $var);
782 }
783 }
784 }
785
786 $this->addGroup($options, $id, $title, $separator);
787
788 if ($other) {
789 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
790 }
791
792 if ($required) {
793 $this->addRule($id,
794 ts('%1 is a required field.', array(1 => $title)),
795 'required'
796 );
797 }
798 }
799
800 function resetValues() {
801 $data = $this->controller->container();
802 $data['values'][$this->_name] = array();
803 }
804
805 /**
806 * simple shell that derived classes can call to add buttons to
807 * the form with a customized title for the main Submit
808 *
809 * @param string $title title of the main button
810 * @param string $type button type for the form after processing
811 * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
812 *
813 * @return void
814 * @access public
815 */
816 function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
817 $buttons = array();
818 if ($backType != NULL) {
819 $buttons[] = array(
820 'type' => $backType,
821 'name' => ts('Previous'),
822 );
823 }
824 if ($nextType != NULL) {
825 $nextButton = array(
826 'type' => $nextType,
827 'name' => $title,
828 'isDefault' => TRUE,
829 );
830 if ($submitOnce) {
831 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
832 }
833 $buttons[] = $nextButton;
834 }
835 $this->addButtons($buttons);
836 }
837
838 function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
839 if ($displayTime) {
840 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
841 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
842 } else {
843 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
844 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
845 }
846 }
847
848 function addSelect($name, $label, $prefix = NULL, $required = NULL, $extra = NULL, $select = '- select -') {
849 if ($prefix) {
850 $this->addElement('select', $name . '_id' . $prefix, $label,
851 array(
852 '' => $select) + CRM_Core_OptionGroup::values($name), $extra
853 );
854 if ($required) {
855 $this->addRule($name . '_id' . $prefix, ts('Please select %1', array(1 => $label)), 'required');
856 }
857 }
858 else {
859 $this->addElement('select', $name . '_id', $label,
860 array(
861 '' => $select) + CRM_Core_OptionGroup::values($name), $extra
862 );
863 if ($required) {
864 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
865 }
866 }
867 }
868
869 /**
870 * Add a widget for selecting/editing/creating/copying a profile form
871 *
872 * @param string $name HTML form-element name
873 * @param string $label Printable label
874 * @param string $allowCoreTypes only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'
875 * @param string $allowSubTypes only present a UFGroup if its group_type is compatible with $allowSubypes
876 * @param array $entities
877 */
878 function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities) {
879 // Output widget
880 // FIXME: Instead of adhoc serialization, use a single json_encode()
881 CRM_UF_Page_ProfileEditor::registerProfileScripts();
882 CRM_UF_Page_ProfileEditor::registerSchemas(CRM_Utils_Array::collect('entity_type', $entities));
883 $this->add('text', $name, $label, array(
884 'class' => 'crm-profile-selector',
885 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
886 'data-group-type' => CRM_Core_BAO_UFGroup::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
887 'data-entities' => json_encode($entities),
888 ));
889 }
890
891 function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
892 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
893 // 2. Based on the option, initialise proper editor
894 $editorID = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
895 'editor_id'
896 );
897 $editor = strtolower(CRM_Utils_Array::value($editorID,
cbf48754 898 CRM_Core_OptionGroup::values('wysiwyg_editor')
6a488035
TO
899 ));
900 if (!$editor || $forceTextarea) {
901 $editor = 'textarea';
902 }
903 if ($editor == 'joomla default editor') {
904 $editor = 'joomlaeditor';
905 }
906
907 if ($editor == 'drupal default editor') {
908 $editor = 'drupalwysiwyg';
909 }
910
911 //lets add the editor as a attribute
912 $attributes['editor'] = $editor;
913
914 $this->addElement($editor, $name, $label, $attributes);
915 $this->assign('editor', $editor);
916
917 // include wysiwyg editor js files
918 $includeWysiwygEditor = FALSE;
919 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
920 if (!$includeWysiwygEditor) {
921 $includeWysiwygEditor = TRUE;
922 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
923 }
924
925 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
926 }
927
928 function addCountry($id, $title, $required = NULL, $extra = NULL) {
929 $this->addElement('select', $id, $title,
930 array(
931 '' => ts('- select -')) + CRM_Core_PseudoConstant::country(), $extra
932 );
933 if ($required) {
934 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
935 }
936 }
937
938 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
939
940 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
941
942 if ($required) {
943 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
944 }
945 }
946
947 function buildAddressBlock($locationId, $title, $phone,
948 $alternatePhone = NULL, $addressRequired = NULL,
949 $phoneRequired = NULL, $altPhoneRequired = NULL,
950 $locationName = NULL
951 ) {
952 if (!$locationName) {
953 $locationName = "location";
954 }
955
956 $config = CRM_Core_Config::singleton();
957 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_Address');
958
959 $location[$locationId]['address']['street_address'] = $this->addElement('text', "{$locationName}[$locationId][address][street_address]", $title,
960 $attributes['street_address']
961 );
962 if ($addressRequired) {
963 $this->addRule("{$locationName}[$locationId][address][street_address]", ts("Please enter the Street Address for %1.", array(1 => $title)), 'required');
964 }
965
c29131ec 966 $location[$locationId]['address']['supplemental_address_1'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_1]", ts('Supplemental Address 1'),
6a488035
TO
967 $attributes['supplemental_address_1']
968 );
c29131ec 969 $location[$locationId]['address']['supplemental_address_2'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_2]", ts('Supplemental Address 2'),
6a488035
TO
970 $attributes['supplemental_address_2']
971 );
972
973 $location[$locationId]['address']['city'] = $this->addElement('text', "{$locationName}[$locationId][address][city]", ts('City'),
974 $attributes['city']
975 );
976 if ($addressRequired) {
977 $this->addRule("{$locationName}[$locationId][address][city]", ts("Please enter the City for %1.", array(1 => $title)), 'required');
978 }
979
980 $location[$locationId]['address']['postal_code'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code]", ts('Zip / Postal Code'),
981 $attributes['postal_code']
982 );
983 if ($addressRequired) {
984 $this->addRule("{$locationName}[$locationId][address][postal_code]", ts("Please enter the Zip/Postal Code for %1.", array(1 => $title)), 'required');
985 }
986
987 $location[$locationId]['address']['postal_code_suffix'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code_suffix]", ts('Add-on Code'),
988 array('size' => 4, 'maxlength' => 12)
989 );
990 $this->addRule("{$locationName}[$locationId][address][postal_code_suffix]", ts('Zip-Plus not valid.'), 'positiveInteger');
991
992 if ($config->includeCounty) {
993 $location[$locationId]['address']['county_id'] = $this->addElement('select', "{$locationName}[$locationId][address][county_id]", ts('County'),
994 array('' => ts('- select -')) + CRM_Core_PseudoConstant::county()
995 );
996 }
997
998 $location[$locationId]['address']['state_province_id'] = $this->addElement('select', "{$locationName}[$locationId][address][state_province_id]", ts('State / Province'),
999 array('' => ts('- select -')) + CRM_Core_PseudoConstant::stateProvince()
1000 );
1001
1002 $location[$locationId]['address']['country_id'] = $this->addElement('select', "{$locationName}[$locationId][address][country_id]", ts('Country'),
1003 array('' => ts('- select -')) + CRM_Core_PseudoConstant::country()
1004 );
1005 if ($addressRequired) {
1006 $this->addRule("{$locationName}[$locationId][address][country_id]", ts("Please select the Country for %1.", array(1 => $title)), 'required');
1007 }
1008
1009
1010 if ($phone) {
1011 $location[$locationId]['phone'][1]['phone'] = $this->addElement('text',
1012 "{$locationName}[$locationId][phone][1][phone]",
1013 $phone,
1014 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
1015 'phone'
1016 )
1017 );
1018 if ($phoneRequired) {
1019 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a value for %1', array(1 => $phone)), 'required');
1020 }
1021 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a valid number for %1', array(1 => $phone)), 'phone');
1022 }
1023
1024 if ($alternatePhone) {
1025 $location[$locationId]['phone'][2]['phone'] = $this->addElement('text',
1026 "{$locationName}[$locationId][phone][2][phone]",
1027 $alternatePhone,
1028 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
1029 'phone'
1030 )
1031 );
1032 if ($alternatePhoneRequired) {
1033 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a value for %1', array(1 => $alternatePhone)), 'required');
1034 }
1035 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a valid number for %1', array(1 => $alternatePhone)), 'phone');
1036 }
1037 }
1038
1039 public function getRootTitle() {
1040 return NULL;
1041 }
1042
1043 public function getCompleteTitle() {
1044 return $this->getRootTitle() . $this->getTitle();
1045 }
1046
1047 static function &getTemplate() {
1048 return self::$_template;
1049 }
1050
1051 function addUploadElement($elementName) {
1052 $uploadNames = $this->get('uploadNames');
1053 if (!$uploadNames) {
1054 $uploadNames = array();
1055 }
1056 if (is_array($elementName)) {
1057 foreach ($elementName as $name) {
1058 if (!in_array($name, $uploadNames)) {
1059 $uploadNames[] = $name;
1060 }
1061 }
1062 }
1063 else {
1064 if (!in_array($elementName, $uploadNames)) {
1065 $uploadNames[] = $elementName;
1066 }
1067 }
1068 $this->set('uploadNames', $uploadNames);
1069
1070 $config = CRM_Core_Config::singleton();
1071 if (!empty($uploadNames)) {
1072 $this->controller->addUploadAction($config->customFileUploadDir, $uploadNames);
1073 }
1074 }
1075
1076 function buttonType() {
1077 $uploadNames = $this->get('uploadNames');
1078 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ? 'upload' : 'next';
1079 $this->assign('buttonType', $buttonType);
1080 return $buttonType;
1081 }
1082
1083 function getVar($name) {
1084 return isset($this->$name) ? $this->$name : NULL;
1085 }
1086
1087 function setVar($name, $value) {
1088 $this->$name = $value;
1089 }
1090
1091 /**
1092 * Function to add date
1093 * @param string $name name of the element
1094 * @param string $label label of the element
1095 * @param array $attributes key / value pair
1096 *
1097 // if you need time
1098 * $attributes = array ( 'addTime' => true,
1099 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1100 * );
1101 * @param boolean $required true if required
1102 *
1103 */
1104 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1105 if (CRM_Utils_Array::value('formatType', $attributes)) {
1106 // get actual format
1107 $params = array('name' => $attributes['formatType']);
1108 $values = array();
1109
1110 // cache date information
1111 static $dateFormat;
1112 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1113 if (!CRM_Utils_Array::value($key, $dateFormat)) {
1114 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1115 $dateFormat[$key] = $values;
1116 }
1117 else {
1118 $values = $dateFormat[$key];
1119 }
1120
1121 if ($values['date_format']) {
1122 $attributes['format'] = $values['date_format'];
1123 }
1124
1125 if (CRM_Utils_Array::value('time_format', $values)) {
1126 $attributes['timeFormat'] = $values['time_format'];
1127 }
1128 $attributes['startOffset'] = $values['start'];
1129 $attributes['endOffset'] = $values['end'];
1130 }
1131
1132 $config = CRM_Core_Config::singleton();
1133 if (!CRM_Utils_Array::value('format', $attributes)) {
1134 $attributes['format'] = $config->dateInputFormat;
1135 }
1136
1137 if (!isset($attributes['startOffset'])) {
1138 $attributes['startOffset'] = 10;
1139 }
1140
1141 if (!isset($attributes['endOffset'])) {
1142 $attributes['endOffset'] = 10;
1143 }
1144
1145 $this->add('text', $name, $label, $attributes);
1146
1147 if (CRM_Utils_Array::value('addTime', $attributes) ||
1148 CRM_Utils_Array::value('timeFormat', $attributes)
1149 ) {
1150
1151 if (!isset($attributes['timeFormat'])) {
1152 $timeFormat = $config->timeInputFormat;
1153 }
1154 else {
1155 $timeFormat = $attributes['timeFormat'];
1156 }
1157
1158 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1159 if ($timeFormat) {
1160 $show24Hours = TRUE;
1161 if ($timeFormat == 1) {
1162 $show24Hours = FALSE;
1163 }
1164
1165 //CRM-6664 -we are having time element name
1166 //in either flat string or an array format.
1167 $elementName = $name . '_time';
1168 if (substr($name, -1) == ']') {
1169 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1170 }
1171
1172 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1173 }
1174 }
1175
1176 if ($required) {
1177 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1178 if (CRM_Utils_Array::value('addTime', $attributes) && CRM_Utils_Array::value('addTimeRequired', $attributes)) {
1179 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1180 }
1181 }
1182 }
1183
1184 /**
1185 * Function that will add date and time
1186 */
1187 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1188 $addTime = array('addTime' => TRUE);
1189 if (is_array($attributes)) {
1190 $attributes = array_merge($attributes, $addTime);
1191 }
1192 else {
1193 $attributes = $addTime;
1194 }
1195
1196 $this->addDate($name, $label, $required, $attributes);
1197 }
1198
1199 /**
1200 * add a currency and money element to the form
1201 */
1202 function addMoney($name,
1203 $label,
1204 $required = FALSE,
1205 $attributes = NULL,
1206 $addCurrency = TRUE,
1207 $currencyName = 'currency',
1208 $defaultCurrency = NULL,
1209 $freezeCurrency = FALSE
1210 ) {
1211 $element = $this->add('text', $name, $label, $attributes, $required);
1212 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1213
1214 if ($addCurrency) {
1215 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1216 }
1217
1218 return $element;
1219 }
1220
1221 /**
1222 * add currency element to the form
1223 */
1224 function addCurrency($name = 'currency',
1225 $label = NULL,
1226 $required = TRUE,
1227 $defaultCurrency = NULL,
1228 $freezeCurrency = FALSE
1229 ) {
1230 $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
1231 if (!$required) {
1232 $currencies = array(
1233 '' => ts('- select -')) + $currencies;
1234 }
1235 $ele = $this->add('select', $name, $label, $currencies, $required);
1236 if ($freezeCurrency) {
1237 $ele->freeze();
1238 }
1239 if (!$defaultCurrency) {
1240 $config = CRM_Core_Config::singleton();
1241 $defaultCurrency = $config->defaultCurrency;
1242 }
1243 $this->setDefaults(array($name => $defaultCurrency));
1244 }
1245
5d86176b 1246 /**
1247 * Convert all date fields within the params to mysql date ready for the
1248 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1249 * and if time is defined it is incorporated
1250 *
1251 * @param array $params input params from the form
1252 *
1253 * @todo it would probably be better to work on $this->_params than a passed array
1254 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1255 * handling from BAO
1256 */
1257 function convertDateFieldsToMySQL(&$params){
1258 foreach ($this->_dateFields as $fieldName => $specs){
1259 if(!empty($params[$fieldName])){
1260 $params[$fieldName] = CRM_Utils_Date::isoToMysql(
1261 CRM_Utils_Date::processDate(
1262 $params[$fieldName],
1263 CRM_Utils_Array::value("{$fieldName}_time", $params), TRUE)
1264 );
1265 }
1266 else{
1267 if(isset($specs['default'])){
1268 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1269 }
1270 }
1271 }
1272 }
1273
6a488035
TO
1274 function removeFileRequiredRules($elementName) {
1275 $this->_required = array_diff($this->_required, array($elementName));
1276 if (isset($this->_rules[$elementName])) {
1277 foreach ($this->_rules[$elementName] as $index => $ruleInfo) {
1278 if ($ruleInfo['type'] == 'uploadedfile') {
1279 unset($this->_rules[$elementName][$index]);
1280 }
1281 }
1282 if (empty($this->_rules[$elementName])) {
1283 unset($this->_rules[$elementName]);
1284 }
1285 }
1286 }
1287
1288 /**
1289 * Function that can be defined in Form to override or
1290 * perform specific action on cancel action
1291 *
1292 * @access public
1293 */
1294 function cancelAction() {}
7cb3d4f0
CW
1295
1296 /**
1297 * Helper function to verify that required fields have been filled
1298 * Typically called within the scope of a FormRule function
1299 */
1300 static function validateMandatoryFields($fields, $values, &$errors) {
1301 foreach ($fields as $name => $fld) {
1302 if (!empty($fld['is_required']) && CRM_Utils_System::isNull(CRM_Utils_Array::value($name, $values))) {
1303 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1304 }
1305 }
1306 }
da8d9879
DG
1307
1308/**
1309 * Get contact if for a form object. Prioritise
1310 * - cid in URL if 0 (on behalf on someoneelse)
1311 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1312 * - logged in user id if it matches the one in the cid in the URL
1313 * - contact id validated from a checksum from a checksum
1314 * - cid from the url if the caller has ACL permission to view
1315 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1316 *
1317 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1318 */
1319 function getContactID() {
1320 $tempID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
e1ce628e 1321 if(isset($this->_params) && isset($this->_params['select_contact_id'])) {
596bff78 1322 $tempID = $this->_params['select_contact_id'];
1323 }
e1ce628e 1324 if(isset($this->_params, $this->_params[0]) && !empty($this->_params[0]['select_contact_id'])) {
1325 // event form stores as an indexed array, contribution form not so much...
1326 $tempID = $this->_params[0]['select_contact_id'];
1327 }
da8d9879
DG
1328 // force to ignore the authenticated user
1329 if ($tempID === '0') {
1330 return $tempID;
1331 }
1332
596bff78 1333 $userID = $this->getLoggedInUserContactID();
da8d9879
DG
1334
1335 if ($tempID == $userID) {
1336 return $userID;
1337 }
1338
1339 //check if this is a checksum authentication
1340 $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this);
1341 if ($userChecksum) {
1342 //check for anonymous user.
1343 $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($tempID, $userChecksum);
1344 if ($validUser) {
1345 return $tempID;
1346 }
1347 }
1348 // check if user has permission, CRM-12062
1349 else if ($tempID && CRM_Contact_BAO_Contact_Permission::allow($tempID)) {
1350 return $tempID;
1351 }
1352
1353 return $userID;
1354 }
596bff78 1355
1356 /**
1357 * Get the contact id of the logged in user
1358 */
1359 function getLoggedInUserContactID() {
1360 // check if the user is logged in and has a contact ID
1361 $session = CRM_Core_Session::singleton();
1362 return $session->get('userID');
1363 }
1364
1365 /**
1366 * add autoselector field -if user has permission to view contacts
1367 * If adding this to a form you also need to add to the tpl e.g
1368 *
1369 * {if !empty($selectable)}
1370 * <div class="crm-summary-row">
1371 * <div class="crm-label">{$form.select_contact.label}</div>
1372 * <div class="crm-content">
1373 * {$form.select_contact.html}
1374 * </div>
1375 * </div>
1376 * {/if}
1377 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1378 * @param array $field metadata of field to use as selector including
1379 * - name_field
1380 * - id_field
1381 * - url (for ajax lookup)
1382 *
1383 * @todo add data attributes so we can deal with multiple instances on a form
1384 */
1385 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1386 $autoCompleteField = array_merge(array(
1387 'name_field' => 'select_contact',
1388 'id_field' => 'select_contact_id',
1389 'field_text' => ts('Select Contact'),
1390 'show_hide' => TRUE,
25977d86 1391 'show_text' => ts('to select someone already in our database.'),
1392 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
596bff78 1393 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
1394 'max' => civicrm_api3('setting', 'getvalue', array(
1395 'name' => 'search_autocomplete_count',
1396 'group' => 'Search Preferences',
1397 ))
1398 ), $autoCompleteField);
1399
1400 if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
1401 $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
25977d86 1402 $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
1403 $this->assign('selectable', $autoCompleteField['id_field']);
596bff78 1404
25977d86 1405 CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
596bff78 1406 ->addSetting(array(
1407 'form' => array('autocompletes' => $autoCompleteField),
1408 'ids' => array('profile' => $profiles),
1409 ));
1410 }
1411 }
1412
1413 /**
1414 * Add the options appropriate to cid = zero - ie. autocomplete
1415 *
1416 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1417 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1418 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1419 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1420 */
1421 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1422 $this->assign('nocid', TRUE);
1423 $profiles = array();
1424 if($this->_values['custom_pre_id']) {
1425 $profiles[] = $this->_values['custom_pre_id'];
1426 }
1427 if($this->_values['custom_post_id']) {
1428 $profiles[] = $this->_values['custom_post_id'];
1429 }
1430 if($onlinePaymentProcessorEnabled) {
1431 $profiles[] = 'billing';
1432 }
1433 if(!empty($this->_values)) {
1434 $this->addAutoSelector($profiles);
1435 }
1436 }
6a488035
TO
1437}
1438