CRM-10693 - Ajax - Add id to std response
[civicrm-core.git] / CRM / Core / Form.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.4 |
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
39 require_once 'HTML/QuickForm/Page.php';
40 class 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
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
94 /**
95 * cache the smarty template for efficiency reasons
96 *
97 * @var CRM_Core_Smarty
98 */
99 static protected $_template;
100
101 /**
102 * What to return to the client if in ajax mode (snippet=6)
103 *
104 * @var array
105 */
106 public $ajaxResponse = array();
107
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 }
169
170 $this->assign('snippet', (int) CRM_Utils_Array::value('snippet', $_REQUEST));
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();
271 $this->postProcessHook();
272
273 // Respond with JSON if in AJAX context
274 if (!empty($_REQUEST['snippet']) && $_REQUEST['snippet'] == CRM_Core_Smarty::PRINT_JSON) {
275 $this->ajaxResponse['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller->getButtonName());
276 $this->ajaxResponse['action'] = $this->_action;
277 if (isset($this->_id) || isset($this->id)) {
278 $this->ajaxResponse['id'] = isset($this->id) ? $this->id : $this->_id;
279 }
280 CRM_Core_Page_AJAX::returnJsonResponse($this->ajaxResponse);
281 }
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 */
306 function buildQuickForm() {}
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);
379
380 }
381
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) {
385 $this->addElement('hidden', 'entryURL', $this->controller->_entryURL);
386 }
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) {
425 $attrs = array('class' => 'form-submit default');
426 }
427 else {
428 $attrs = array('class' => 'form-submit');
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 {
439 if (CRM_Utils_Array::value('subName', $button)) {
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 }
452 if (CRM_Utils_Array::value('isDefault', $button)) {
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
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
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
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
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,
944 CRM_Core_OptionGroup::values('wysiwyg_editor')
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
964 $includeWysiwygEditor = FALSE;
965 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
966 if (!$includeWysiwygEditor) {
967 $includeWysiwygEditor = TRUE;
968 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
969 }
970
971 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
972 }
973
974 function addCountry($id, $title, $required = NULL, $extra = NULL) {
975 $this->addElement('select', $id, $title,
976 array(
977 '' => ts('- select -')) + CRM_Core_PseudoConstant::country(), $extra
978 );
979 if ($required) {
980 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
981 }
982 }
983
984 function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
985
986 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
987
988 if ($required) {
989 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
990 }
991 }
992
993 function buildAddressBlock($locationId, $title, $phone,
994 $alternatePhone = NULL, $addressRequired = NULL,
995 $phoneRequired = NULL, $altPhoneRequired = NULL,
996 $locationName = NULL
997 ) {
998 if (!$locationName) {
999 $locationName = "location";
1000 }
1001
1002 $config = CRM_Core_Config::singleton();
1003 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_Address');
1004
1005 $location[$locationId]['address']['street_address'] = $this->addElement('text', "{$locationName}[$locationId][address][street_address]", $title,
1006 $attributes['street_address']
1007 );
1008 if ($addressRequired) {
1009 $this->addRule("{$locationName}[$locationId][address][street_address]", ts("Please enter the Street Address for %1.", array(1 => $title)), 'required');
1010 }
1011
1012 $location[$locationId]['address']['supplemental_address_1'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_1]", ts('Supplemental Address 1'),
1013 $attributes['supplemental_address_1']
1014 );
1015 $location[$locationId]['address']['supplemental_address_2'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_2]", ts('Supplemental Address 2'),
1016 $attributes['supplemental_address_2']
1017 );
1018
1019 $location[$locationId]['address']['city'] = $this->addElement('text', "{$locationName}[$locationId][address][city]", ts('City'),
1020 $attributes['city']
1021 );
1022 if ($addressRequired) {
1023 $this->addRule("{$locationName}[$locationId][address][city]", ts("Please enter the City for %1.", array(1 => $title)), 'required');
1024 }
1025
1026 $location[$locationId]['address']['postal_code'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code]", ts('Zip / Postal Code'),
1027 $attributes['postal_code']
1028 );
1029 if ($addressRequired) {
1030 $this->addRule("{$locationName}[$locationId][address][postal_code]", ts("Please enter the Zip/Postal Code for %1.", array(1 => $title)), 'required');
1031 }
1032
1033 $location[$locationId]['address']['postal_code_suffix'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code_suffix]", ts('Add-on Code'),
1034 array('size' => 4, 'maxlength' => 12)
1035 );
1036 $this->addRule("{$locationName}[$locationId][address][postal_code_suffix]", ts('Zip-Plus not valid.'), 'positiveInteger');
1037
1038 if ($config->includeCounty) {
1039 $location[$locationId]['address']['county_id'] = $this->addElement('select', "{$locationName}[$locationId][address][county_id]", ts('County'),
1040 array('' => ts('- select -')) + CRM_Core_PseudoConstant::county()
1041 );
1042 }
1043
1044 $location[$locationId]['address']['state_province_id'] = $this->addElement('select', "{$locationName}[$locationId][address][state_province_id]", ts('State / Province'),
1045 array('' => ts('- select -')) + CRM_Core_PseudoConstant::stateProvince()
1046 );
1047
1048 $location[$locationId]['address']['country_id'] = $this->addElement('select', "{$locationName}[$locationId][address][country_id]", ts('Country'),
1049 array('' => ts('- select -')) + CRM_Core_PseudoConstant::country()
1050 );
1051 if ($addressRequired) {
1052 $this->addRule("{$locationName}[$locationId][address][country_id]", ts("Please select the Country for %1.", array(1 => $title)), 'required');
1053 }
1054
1055
1056 if ($phone) {
1057 $location[$locationId]['phone'][1]['phone'] = $this->addElement('text',
1058 "{$locationName}[$locationId][phone][1][phone]",
1059 $phone,
1060 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
1061 'phone'
1062 )
1063 );
1064 if ($phoneRequired) {
1065 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a value for %1', array(1 => $phone)), 'required');
1066 }
1067 $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a valid number for %1', array(1 => $phone)), 'phone');
1068 }
1069
1070 if ($alternatePhone) {
1071 $location[$locationId]['phone'][2]['phone'] = $this->addElement('text',
1072 "{$locationName}[$locationId][phone][2][phone]",
1073 $alternatePhone,
1074 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
1075 'phone'
1076 )
1077 );
1078 if ($alternatePhoneRequired) {
1079 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a value for %1', array(1 => $alternatePhone)), 'required');
1080 }
1081 $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a valid number for %1', array(1 => $alternatePhone)), 'phone');
1082 }
1083 }
1084
1085 public function getRootTitle() {
1086 return NULL;
1087 }
1088
1089 public function getCompleteTitle() {
1090 return $this->getRootTitle() . $this->getTitle();
1091 }
1092
1093 static function &getTemplate() {
1094 return self::$_template;
1095 }
1096
1097 function addUploadElement($elementName) {
1098 $uploadNames = $this->get('uploadNames');
1099 if (!$uploadNames) {
1100 $uploadNames = array();
1101 }
1102 if (is_array($elementName)) {
1103 foreach ($elementName as $name) {
1104 if (!in_array($name, $uploadNames)) {
1105 $uploadNames[] = $name;
1106 }
1107 }
1108 }
1109 else {
1110 if (!in_array($elementName, $uploadNames)) {
1111 $uploadNames[] = $elementName;
1112 }
1113 }
1114 $this->set('uploadNames', $uploadNames);
1115
1116 $config = CRM_Core_Config::singleton();
1117 if (!empty($uploadNames)) {
1118 $this->controller->addUploadAction($config->customFileUploadDir, $uploadNames);
1119 }
1120 }
1121
1122 function buttonType() {
1123 $uploadNames = $this->get('uploadNames');
1124 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ? 'upload' : 'next';
1125 $this->assign('buttonType', $buttonType);
1126 return $buttonType;
1127 }
1128
1129 function getVar($name) {
1130 return isset($this->$name) ? $this->$name : NULL;
1131 }
1132
1133 function setVar($name, $value) {
1134 $this->$name = $value;
1135 }
1136
1137 /**
1138 * Function to add date
1139 * @param string $name name of the element
1140 * @param string $label label of the element
1141 * @param array $attributes key / value pair
1142 *
1143 // if you need time
1144 * $attributes = array ( 'addTime' => true,
1145 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1146 * );
1147 * @param boolean $required true if required
1148 *
1149 */
1150 function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1151 if (CRM_Utils_Array::value('formatType', $attributes)) {
1152 // get actual format
1153 $params = array('name' => $attributes['formatType']);
1154 $values = array();
1155
1156 // cache date information
1157 static $dateFormat;
1158 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1159 if (!CRM_Utils_Array::value($key, $dateFormat)) {
1160 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1161 $dateFormat[$key] = $values;
1162 }
1163 else {
1164 $values = $dateFormat[$key];
1165 }
1166
1167 if ($values['date_format']) {
1168 $attributes['format'] = $values['date_format'];
1169 }
1170
1171 if (CRM_Utils_Array::value('time_format', $values)) {
1172 $attributes['timeFormat'] = $values['time_format'];
1173 }
1174 $attributes['startOffset'] = $values['start'];
1175 $attributes['endOffset'] = $values['end'];
1176 }
1177
1178 $config = CRM_Core_Config::singleton();
1179 if (!CRM_Utils_Array::value('format', $attributes)) {
1180 $attributes['format'] = $config->dateInputFormat;
1181 }
1182
1183 if (!isset($attributes['startOffset'])) {
1184 $attributes['startOffset'] = 10;
1185 }
1186
1187 if (!isset($attributes['endOffset'])) {
1188 $attributes['endOffset'] = 10;
1189 }
1190
1191 $this->add('text', $name, $label, $attributes);
1192
1193 if (CRM_Utils_Array::value('addTime', $attributes) ||
1194 CRM_Utils_Array::value('timeFormat', $attributes)
1195 ) {
1196
1197 if (!isset($attributes['timeFormat'])) {
1198 $timeFormat = $config->timeInputFormat;
1199 }
1200 else {
1201 $timeFormat = $attributes['timeFormat'];
1202 }
1203
1204 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1205 if ($timeFormat) {
1206 $show24Hours = TRUE;
1207 if ($timeFormat == 1) {
1208 $show24Hours = FALSE;
1209 }
1210
1211 //CRM-6664 -we are having time element name
1212 //in either flat string or an array format.
1213 $elementName = $name . '_time';
1214 if (substr($name, -1) == ']') {
1215 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1216 }
1217
1218 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1219 }
1220 }
1221
1222 if ($required) {
1223 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1224 if (CRM_Utils_Array::value('addTime', $attributes) && CRM_Utils_Array::value('addTimeRequired', $attributes)) {
1225 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1226 }
1227 }
1228 }
1229
1230 /**
1231 * Function that will add date and time
1232 */
1233 function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1234 $addTime = array('addTime' => TRUE);
1235 if (is_array($attributes)) {
1236 $attributes = array_merge($attributes, $addTime);
1237 }
1238 else {
1239 $attributes = $addTime;
1240 }
1241
1242 $this->addDate($name, $label, $required, $attributes);
1243 }
1244
1245 /**
1246 * add a currency and money element to the form
1247 */
1248 function addMoney($name,
1249 $label,
1250 $required = FALSE,
1251 $attributes = NULL,
1252 $addCurrency = TRUE,
1253 $currencyName = 'currency',
1254 $defaultCurrency = NULL,
1255 $freezeCurrency = FALSE
1256 ) {
1257 $element = $this->add('text', $name, $label, $attributes, $required);
1258 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1259
1260 if ($addCurrency) {
1261 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1262 }
1263
1264 return $element;
1265 }
1266
1267 /**
1268 * add currency element to the form
1269 */
1270 function addCurrency($name = 'currency',
1271 $label = NULL,
1272 $required = TRUE,
1273 $defaultCurrency = NULL,
1274 $freezeCurrency = FALSE
1275 ) {
1276 $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
1277 if (!$required) {
1278 $currencies = array(
1279 '' => ts('- select -')) + $currencies;
1280 }
1281 $ele = $this->add('select', $name, $label, $currencies, $required);
1282 if ($freezeCurrency) {
1283 $ele->freeze();
1284 }
1285 if (!$defaultCurrency) {
1286 $config = CRM_Core_Config::singleton();
1287 $defaultCurrency = $config->defaultCurrency;
1288 }
1289 $this->setDefaults(array($name => $defaultCurrency));
1290 }
1291
1292 /**
1293 * Convert all date fields within the params to mysql date ready for the
1294 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1295 * and if time is defined it is incorporated
1296 *
1297 * @param array $params input params from the form
1298 *
1299 * @todo it would probably be better to work on $this->_params than a passed array
1300 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1301 * handling from BAO
1302 */
1303 function convertDateFieldsToMySQL(&$params){
1304 foreach ($this->_dateFields as $fieldName => $specs){
1305 if(!empty($params[$fieldName])){
1306 $params[$fieldName] = CRM_Utils_Date::isoToMysql(
1307 CRM_Utils_Date::processDate(
1308 $params[$fieldName],
1309 CRM_Utils_Array::value("{$fieldName}_time", $params), TRUE)
1310 );
1311 }
1312 else{
1313 if(isset($specs['default'])){
1314 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1315 }
1316 }
1317 }
1318 }
1319
1320 function removeFileRequiredRules($elementName) {
1321 $this->_required = array_diff($this->_required, array($elementName));
1322 if (isset($this->_rules[$elementName])) {
1323 foreach ($this->_rules[$elementName] as $index => $ruleInfo) {
1324 if ($ruleInfo['type'] == 'uploadedfile') {
1325 unset($this->_rules[$elementName][$index]);
1326 }
1327 }
1328 if (empty($this->_rules[$elementName])) {
1329 unset($this->_rules[$elementName]);
1330 }
1331 }
1332 }
1333
1334 /**
1335 * Function that can be defined in Form to override or
1336 * perform specific action on cancel action
1337 *
1338 * @access public
1339 */
1340 function cancelAction() {}
1341
1342 /**
1343 * Helper function to verify that required fields have been filled
1344 * Typically called within the scope of a FormRule function
1345 */
1346 static function validateMandatoryFields($fields, $values, &$errors) {
1347 foreach ($fields as $name => $fld) {
1348 if (!empty($fld['is_required']) && CRM_Utils_System::isNull(CRM_Utils_Array::value($name, $values))) {
1349 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1350 }
1351 }
1352 }
1353
1354 /**
1355 * Get contact if for a form object. Prioritise
1356 * - cid in URL if 0 (on behalf on someoneelse)
1357 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1358 * - logged in user id if it matches the one in the cid in the URL
1359 * - contact id validated from a checksum from a checksum
1360 * - cid from the url if the caller has ACL permission to view
1361 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1362 *
1363 * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
1364 */
1365 function getContactID() {
1366 $tempID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
1367 if(isset($this->_params) && isset($this->_params['select_contact_id'])) {
1368 $tempID = $this->_params['select_contact_id'];
1369 }
1370 if(isset($this->_params, $this->_params[0]) && !empty($this->_params[0]['select_contact_id'])) {
1371 // event form stores as an indexed array, contribution form not so much...
1372 $tempID = $this->_params[0]['select_contact_id'];
1373 }
1374 // force to ignore the authenticated user
1375 if ($tempID === '0') {
1376 return $tempID;
1377 }
1378
1379 $userID = $this->getLoggedInUserContactID();
1380
1381 if ($tempID == $userID) {
1382 return $userID;
1383 }
1384
1385 //check if this is a checksum authentication
1386 $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this);
1387 if ($userChecksum) {
1388 //check for anonymous user.
1389 $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($tempID, $userChecksum);
1390 if ($validUser) {
1391 return $tempID;
1392 }
1393 }
1394 // check if user has permission, CRM-12062
1395 else if ($tempID && CRM_Contact_BAO_Contact_Permission::allow($tempID)) {
1396 return $tempID;
1397 }
1398
1399 return $userID;
1400 }
1401
1402 /**
1403 * Get the contact id of the logged in user
1404 */
1405 function getLoggedInUserContactID() {
1406 // check if the user is logged in and has a contact ID
1407 $session = CRM_Core_Session::singleton();
1408 return $session->get('userID');
1409 }
1410
1411 /**
1412 * add autoselector field -if user has permission to view contacts
1413 * If adding this to a form you also need to add to the tpl e.g
1414 *
1415 * {if !empty($selectable)}
1416 * <div class="crm-summary-row">
1417 * <div class="crm-label">{$form.select_contact.label}</div>
1418 * <div class="crm-content">
1419 * {$form.select_contact.html}
1420 * </div>
1421 * </div>
1422 * {/if}
1423 * @param array $profiles ids of profiles that are on the form (to be autofilled)
1424 * @param array $field metadata of field to use as selector including
1425 * - name_field
1426 * - id_field
1427 * - url (for ajax lookup)
1428 *
1429 * @todo add data attributes so we can deal with multiple instances on a form
1430 */
1431 function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1432 $autoCompleteField = array_merge(array(
1433 'name_field' => 'select_contact',
1434 'id_field' => 'select_contact_id',
1435 'field_text' => ts('Select Contact'),
1436 'show_hide' => TRUE,
1437 'show_text' => ts('to select someone already in our database.'),
1438 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
1439 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
1440 'max' => civicrm_api3('setting', 'getvalue', array(
1441 'name' => 'search_autocomplete_count',
1442 'group' => 'Search Preferences',
1443 ))
1444 ), $autoCompleteField);
1445
1446 if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
1447 $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
1448 $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
1449 $this->assign('selectable', $autoCompleteField['id_field']);
1450
1451 CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
1452 ->addSetting(array(
1453 'form' => array('autocompletes' => $autoCompleteField),
1454 'ids' => array('profile' => $profiles),
1455 ));
1456 }
1457 }
1458
1459 /**
1460 * Add the options appropriate to cid = zero - ie. autocomplete
1461 *
1462 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1463 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1464 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1465 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1466 */
1467 function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1468 $this->assign('nocid', TRUE);
1469 $profiles = array();
1470 if($this->_values['custom_pre_id']) {
1471 $profiles[] = $this->_values['custom_pre_id'];
1472 }
1473 if($this->_values['custom_post_id']) {
1474 $profiles[] = $this->_values['custom_post_id'];
1475 }
1476 if($onlinePaymentProcessorEnabled) {
1477 $profiles[] = 'billing';
1478 }
1479 if(!empty($this->_values)) {
1480 $this->addAutoSelector($profiles);
1481 }
1482 }
1483
1484 /**
1485 * Set default values on form for given contact (or no contact defaults)
1486 * @param mixed $profile_id (can be id, or profile name)
1487 * @param integer $contactID
1488 */
1489 function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1490 try{
1491 $defaults = civicrm_api3('profile', 'getsingle', array(
1492 'profile_id' => (array) $profile_id,
1493 'contact_id' => $contactID,
1494 ));
1495 return $defaults;
1496 }
1497 catch (Exception $e) {
1498 // the try catch block gives us silent failure -not 100% sure this is a good idea
1499 // as silent failures are often worse than noisy ones
1500 return array();
1501 }
1502 }
1503 }
1504