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