fixed codestye issues
[civicrm-core.git] / CRM / Core / Form.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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-2014
35 * $Id$
36 *
37 */
38
39 require_once 'HTML/QuickForm/Page.php';
40
41 /**
42 * Class CRM_Core_Form
43 */
44 class CRM_Core_Form extends HTML_QuickForm_Page {
45
46 /**
47 * The state object that this form belongs to
48 * @var object
49 */
50 protected $_state;
51
52 /**
53 * The name of this form
54 * @var string
55 */
56 protected $_name;
57
58 /**
59 * The title of this form
60 * @var string
61 */
62 protected $_title = NULL;
63
64 /**
65 * The options passed into this form
66 * @var mixed
67 */
68 protected $_options = NULL;
69
70 /**
71 * The mode of operation for this form
72 * @var int
73 */
74 protected $_action;
75
76 /**
77 * The renderer used for this form
78 *
79 * @var object
80 */
81 protected $_renderer;
82
83 /**
84 * An array to hold a list of datefields on the form
85 * so that they can be converted to ISO in a consistent manner
86 *
87 * @var array
88 *
89 * e.g on a form declare $_dateFields = array(
90 * 'receive_date' => array('default' => 'now'),
91 * );
92 * then in postProcess call $this->convertDateFieldsToMySQL($formValues)
93 * to have the time field re-incorporated into the field & 'now' set if
94 * no value has been passed in
95 */
96 protected $_dateFields = array();
97
98 /**
99 * Cache the smarty template for efficiency reasons
100 *
101 * @var CRM_Core_Smarty
102 */
103 static protected $_template;
104
105 /**
106 * Indicate if this form should warn users of unsaved changes
107 */
108 protected $unsavedChangesWarn;
109
110 /**
111 * What to return to the client if in ajax mode (snippet=json)
112 *
113 * @var array
114 */
115 public $ajaxResponse = array();
116
117 /**
118 * Url path used to reach this page
119 *
120 * @var array
121 */
122 public $urlPath = array();
123
124 /**
125 * @var CRM_Core_Controller
126 */
127 public $controller;
128
129 /**
130 * Api entity name
131 *
132 * @var string
133 */
134 public $entityName;
135
136 /**
137 * Constants for attributes for various form elements
138 * attempt to standardize on the number of variations that we
139 * use of the below form elements
140 *
141 * @var const string
142 */
143 const ATTR_SPACING = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
144
145 /**
146 * All checkboxes are defined with a common prefix. This allows us to
147 * have the same javascript to check / clear all the checkboxes etc
148 * If u have multiple groups of checkboxes, you will need to give them different
149 * ids to avoid potential name collision
150 *
151 * @var string|int
152 */
153 const CB_PREFIX = 'mark_x_', CB_PREFIY = 'mark_y_', CB_PREFIZ = 'mark_z_', CB_PREFIX_LEN = 7;
154
155 /**
156 * @internal to keep track of chain-select fields
157 * @var array
158 */
159 private $_chainSelectFields = array();
160
161 /**
162 * Constructor for the basic form page.
163 *
164 * We should not use QuickForm directly. This class provides a lot
165 * of default convenient functions, rules and buttons
166 *
167 * @param object $state
168 * State associated with this form.
169 * @param \const|\enum|int $action The mode the form is operating in (None/Create/View/Update/Delete)
170 * @param string $method
171 * The type of http method used (GET/POST).
172 * @param string $name
173 * The name of the form if different from class name.
174 *
175 * @return \CRM_Core_Form
176 */
177 public function __construct(
178 $state = NULL,
179 $action = CRM_Core_Action::NONE,
180 $method = 'post',
181 $name = NULL
182 ) {
183
184 if ($name) {
185 $this->_name = $name;
186 }
187 else {
188 // CRM-15153 - FIXME this name translates to a DOM id and is not always unique!
189 $this->_name = CRM_Utils_String::getClassName(CRM_Utils_System::getClassName($this));
190 }
191
192 $this->HTML_QuickForm_Page($this->_name, $method);
193
194 $this->_state =& $state;
195 if ($this->_state) {
196 $this->_state->setName($this->_name);
197 }
198 $this->_action = (int) $action;
199
200 $this->registerRules();
201
202 // let the constructor initialize this, should happen only once
203 if (!isset(self::$_template)) {
204 self::$_template = CRM_Core_Smarty::singleton();
205 }
206 // Workaround for CRM-15153 - give each form a reasonably unique css class
207 $this->addClass(CRM_Utils_System::getClassName($this));
208
209 $this->assign('snippet', CRM_Utils_Array::value('snippet', $_GET));
210 }
211
212 public static function generateID() {
213 }
214
215 /**
216 * Add one or more css classes to the form
217 * @param string $className
218 */
219 public function addClass($className) {
220 $classes = $this->getAttribute('class');
221 $this->setAttribute('class', ($classes ? "$classes " : '') . $className);
222 }
223
224 /**
225 * Register all the standard rules that most forms potentially use.
226 *
227 * @return void
228 */
229 public function registerRules() {
230 static $rules = array(
231 'title',
232 'longTitle',
233 'variable',
234 'qfVariable',
235 'phone',
236 'integer',
237 'query',
238 'url',
239 'wikiURL',
240 'domain',
241 'numberOfDigit',
242 'date',
243 'currentDate',
244 'asciiFile',
245 'htmlFile',
246 'utf8File',
247 'objectExists',
248 'optionExists',
249 'postalCode',
250 'money',
251 'positiveInteger',
252 'xssString',
253 'fileExists',
254 'autocomplete',
255 'validContact',
256 );
257
258 foreach ($rules as $rule) {
259 $this->registerRule($rule, 'callback', $rule, 'CRM_Utils_Rule');
260 }
261 }
262
263 /**
264 * Simple easy to use wrapper around addElement. Deal with
265 * simple validation rules
266 *
267 * @param string $type
268 * @param string $name
269 * @param string $label
270 * @param string|array $attributes (options for select elements)
271 * @param bool $required
272 * @param array $extra
273 * (attributes for select elements).
274 *
275 * @return HTML_QuickForm_Element could be an error object
276 */
277 public function &add(
278 $type, $name, $label = '',
279 $attributes = '', $required = FALSE, $extra = NULL
280 ) {
281 if ($type == 'select' && is_array($extra)) {
282 // Normalize this property
283 if (!empty($extra['multiple'])) {
284 $extra['multiple'] = 'multiple';
285 }
286 else {
287 unset($extra['multiple']);
288 }
289 // Add placeholder option for select
290 if (isset($extra['placeholder'])) {
291 if ($extra['placeholder'] === TRUE) {
292 $extra['placeholder'] = $required ? ts('- select -') : ts('- none -');
293 }
294 if (($extra['placeholder'] || $extra['placeholder'] === '') && empty($extra['multiple']) && is_array($attributes) && !isset($attributes[''])) {
295 $attributes = array('' => $extra['placeholder']) + $attributes;
296 }
297 }
298 }
299 $element = $this->addElement($type, $name, $label, $attributes, $extra);
300 if (HTML_QuickForm::isError($element)) {
301 CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
302 }
303
304 if ($required) {
305 if ($type == 'file') {
306 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'uploadedfile');
307 }
308 else {
309 $error = $this->addRule($name, ts('%1 is a required field.', array(1 => $label)), 'required');
310 }
311 if (HTML_QuickForm::isError($error)) {
312 CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
313 }
314 }
315
316 return $element;
317 }
318
319 /**
320 * called before buildForm. Any pre-processing that
321 * needs to be done for buildForm should be done here
322 *
323 * This is a virtual function and should be redefined if needed
324 *
325 *
326 * @return void
327 */
328 public function preProcess() {
329 }
330
331 /**
332 * called after the form is validated. Any
333 * processing of form state etc should be done in this function.
334 * Typically all processing associated with a form should be done
335 * here and relevant state should be stored in the session
336 *
337 * This is a virtual function and should be redefined if needed
338 *
339 *
340 * @return void
341 */
342 public function postProcess() {
343 }
344
345 /**
346 * just a wrapper, so that we can call all the hook functions
347 * @param bool $allowAjax
348 * FIXME: This feels kind of hackish, ideally we would take the json-related code from this function.
349 * and bury it deeper down in the controller
350 */
351 public function mainProcess($allowAjax = TRUE) {
352 $this->postProcess();
353 $this->postProcessHook();
354
355 // Respond with JSON if in AJAX context (also support legacy value '6')
356 if ($allowAjax && !empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(
357 CRM_Core_Smarty::PRINT_JSON,
358 6,
359 ))
360 ) {
361 $this->ajaxResponse['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller->getButtonName());
362 $this->ajaxResponse['action'] = $this->_action;
363 if (isset($this->_id) || isset($this->id)) {
364 $this->ajaxResponse['id'] = isset($this->id) ? $this->id : $this->_id;
365 }
366 CRM_Core_Page_AJAX::returnJsonResponse($this->ajaxResponse);
367 }
368 }
369
370 /**
371 * The postProcess hook is typically called by the framework.
372 * However in a few cases, the form exits or redirects early in which
373 * case it needs to call this function so other modules can do the needful
374 * Calling this function directly should be avoided if possible. In general a
375 * better way is to do setUserContext so the framework does the redirect
376 */
377 public function postProcessHook() {
378 CRM_Utils_Hook::postProcess(get_class($this), $this);
379 }
380
381 /**
382 * This virtual function is used to build the form. It replaces the
383 * buildForm associated with QuickForm_Page. This allows us to put
384 * preProcess in front of the actual form building routine
385 *
386 *
387 * @return void
388 */
389 public function buildQuickForm() {
390 }
391
392 /**
393 * This virtual function is used to set the default values of
394 * various form elements
395 *
396 * access public
397 *
398 * @return array|NULL
399 * reference to the array of default values
400 */
401 public function setDefaultValues() {
402 return NULL;
403 }
404
405 /**
406 * This is a virtual function that adds group and global rules to
407 * the form. Keeping it distinct from the form to keep code small
408 * and localized in the form building code
409 *
410 *
411 * @return void
412 */
413 public function addRules() {
414 }
415
416 /**
417 * Performs the server side validation.
418 * @since 1.0
419 * @return bool
420 * true if no error found
421 * @throws HTML_QuickForm_Error
422 */
423 public function validate() {
424 $error = parent::validate();
425
426 $this->validateChainSelectFields();
427
428 $hookErrors = CRM_Utils_Hook::validate(
429 get_class($this),
430 $this->_submitValues,
431 $this->_submitFiles,
432 $this
433 );
434
435 if (!is_array($hookErrors)) {
436 $hookErrors = array();
437 }
438
439 CRM_Utils_Hook::validateForm(
440 get_class($this),
441 $this->_submitValues,
442 $this->_submitFiles,
443 $this,
444 $hookErrors
445 );
446
447 if (!empty($hookErrors)) {
448 $this->_errors += $hookErrors;
449 }
450
451 return (0 == count($this->_errors));
452 }
453
454 /**
455 * Core function that builds the form. We redefine this function
456 * here and expect all CRM forms to build their form in the function
457 * buildQuickForm.
458 */
459 public function buildForm() {
460 $this->_formBuilt = TRUE;
461
462 $this->preProcess();
463
464 CRM_Utils_Hook::preProcess(get_class($this), $this);
465
466 $this->assign('translatePermission', CRM_Core_Permission::check('translate CiviCRM'));
467
468 if (
469 $this->controller->_key &&
470 $this->controller->_generateQFKey
471 ) {
472 $this->addElement('hidden', 'qfKey', $this->controller->_key);
473 $this->assign('qfKey', $this->controller->_key);
474
475 }
476
477 // _generateQFKey suppresses the qfKey generation on form snippets that
478 // are part of other forms, hence we use that to avoid adding entryURL
479 if ($this->controller->_generateQFKey && $this->controller->_entryURL) {
480 $this->addElement('hidden', 'entryURL', $this->controller->_entryURL);
481 }
482
483 $this->buildQuickForm();
484
485 $defaults = $this->setDefaultValues();
486 unset($defaults['qfKey']);
487
488 if (!empty($defaults)) {
489 $this->setDefaults($defaults);
490 }
491
492 // call the form hook
493 // also call the hook function so any modules can set thier own custom defaults
494 // the user can do both the form and set default values with this hook
495 CRM_Utils_Hook::buildForm(get_class($this), $this);
496
497 $this->addRules();
498
499 //Set html data-attribute to enable warning user of unsaved changes
500 if ($this->unsavedChangesWarn === TRUE
501 || (!isset($this->unsavedChangesWarn)
502 && ($this->_action & CRM_Core_Action::ADD || $this->_action & CRM_Core_Action::UPDATE)
503 )
504 ) {
505 $this->setAttribute('data-warn-changes', 'true');
506 }
507 }
508
509 /**
510 * Add default Next / Back buttons
511 *
512 * @param array $params
513 * Array of associative arrays in the order in which the buttons should be
514 * displayed. The associate array has 3 fields: 'type', 'name' and 'isDefault'
515 * The base form class will define a bunch of static arrays for commonly used
516 * formats.
517 *
518 * @return void
519 */
520 public function addButtons($params) {
521 $prevnext = $spacing = array();
522 foreach ($params as $button) {
523 $attrs = array('class' => 'crm-form-submit') + (array) CRM_Utils_Array::value('js', $button);
524
525 if (!empty($button['class'])) {
526 $attrs['class'] .= ' ' . $button['class'];
527 }
528
529 if (!empty($button['isDefault'])) {
530 $attrs['class'] .= ' default';
531 }
532
533 if (in_array($button['type'], array('upload', 'next', 'submit', 'done', 'process', 'refresh'))) {
534 $attrs['class'] .= ' validate';
535 $defaultIcon = 'check';
536 }
537 else {
538 $attrs['class'] .= ' cancel';
539 $defaultIcon = $button['type'] == 'back' ? 'triangle-1-w' : 'close';
540 }
541
542 if ($button['type'] === 'reset') {
543 $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
544 }
545 else {
546 if (!empty($button['subName'])) {
547 if ($button['subName'] == 'new') {
548 $defaultIcon = 'plus';
549 }
550 if ($button['subName'] == 'done') {
551 $defaultIcon = 'circle-check';
552 }
553 if ($button['subName'] == 'next') {
554 $defaultIcon = 'circle-triangle-e';
555 }
556 }
557
558 if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
559 $attrs['accesskey'] = 'S';
560 }
561 $icon = CRM_Utils_Array::value('icon', $button, $defaultIcon);
562 if ($icon) {
563 $attrs['crm-icon'] = $icon;
564 }
565 $buttonName = $this->getButtonName($button['type'], CRM_Utils_Array::value('subName', $button));
566 $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
567 }
568 if (!empty($button['isDefault'])) {
569 $this->setDefaultAction($button['type']);
570 }
571
572 // if button type is upload, set the enctype
573 if ($button['type'] == 'upload') {
574 $this->updateAttributes(array('enctype' => 'multipart/form-data'));
575 $this->setMaxFileSize();
576 }
577
578 // hack - addGroup uses an array to express variable spacing, read from the last element
579 $spacing[] = CRM_Utils_Array::value('spacing', $button, self::ATTR_SPACING);
580 }
581 $this->addGroup($prevnext, 'buttons', '', $spacing, FALSE);
582 }
583
584 /**
585 * Getter function for Name.
586 *
587 * @return string
588 */
589 public function getName() {
590 return $this->_name;
591 }
592
593 /**
594 * Getter function for State.
595 *
596 * @return object
597 */
598 public function &getState() {
599 return $this->_state;
600 }
601
602 /**
603 * Getter function for StateType.
604 *
605 * @return int
606 */
607 public function getStateType() {
608 return $this->_state->getType();
609 }
610
611 /**
612 * Getter function for title. Should be over-ridden by derived class
613 *
614 * @return string
615 */
616 public function getTitle() {
617 return $this->_title ? $this->_title : ts('ERROR: Title is not Set');
618 }
619
620 /**
621 * Setter function for title.
622 *
623 * @param string $title
624 * The title of the form.
625 *
626 * @return void
627 */
628 public function setTitle($title) {
629 $this->_title = $title;
630 }
631
632 /**
633 * Setter function for options.
634 *
635 * @param mixed $options
636 *
637 * @return void
638 */
639 public function setOptions($options) {
640 $this->_options = $options;
641 }
642
643 /**
644 * Getter function for link.
645 *
646 * @return string
647 */
648 public function getLink() {
649 $config = CRM_Core_Config::singleton();
650 return CRM_Utils_System::url($_GET[$config->userFrameworkURLVar],
651 '_qf_' . $this->_name . '_display=true'
652 );
653 }
654
655 /**
656 * Boolean function to determine if this is a one form page.
657 *
658 * @return bool
659 */
660 public function isSimpleForm() {
661 return $this->_state->getType() & (CRM_Core_State::START | CRM_Core_State::FINISH);
662 }
663
664 /**
665 * Getter function for Form Action.
666 *
667 * @return string
668 */
669 public function getFormAction() {
670 return $this->_attributes['action'];
671 }
672
673 /**
674 * Setter function for Form Action.
675 *
676 * @param string $action
677 *
678 * @return void
679 */
680 public function setFormAction($action) {
681 $this->_attributes['action'] = $action;
682 }
683
684 /**
685 * Render form and return contents.
686 *
687 * @return string
688 */
689 public function toSmarty() {
690 $this->preProcessChainSelectFields();
691 $renderer = $this->getRenderer();
692 $this->accept($renderer);
693 $content = $renderer->toArray();
694 $content['formName'] = $this->getName();
695 // CRM-15153
696 $content['formClass'] = CRM_Utils_System::getClassName($this);
697 return $content;
698 }
699
700 /**
701 * Getter function for renderer. If renderer is not set
702 * create one and initialize it
703 *
704 * @return object
705 */
706 public function &getRenderer() {
707 if (!isset($this->_renderer)) {
708 $this->_renderer = CRM_Core_Form_Renderer::singleton();
709 }
710 return $this->_renderer;
711 }
712
713 /**
714 * Use the form name to create the tpl file name.
715 *
716 * @return string
717 */
718 public function getTemplateFileName() {
719 $ext = CRM_Extension_System::singleton()->getMapper();
720 if ($ext->isExtensionClass(CRM_Utils_System::getClassName($this))) {
721 $filename = $ext->getTemplateName(CRM_Utils_System::getClassName($this));
722 $tplname = $ext->getTemplatePath(CRM_Utils_System::getClassName($this)) . DIRECTORY_SEPARATOR . $filename;
723 }
724 else {
725 $tplname = strtr(
726 CRM_Utils_System::getClassName($this),
727 array(
728 '_' => DIRECTORY_SEPARATOR,
729 '\\' => DIRECTORY_SEPARATOR,
730 )
731 ) . '.tpl';
732 }
733 return $tplname;
734 }
735
736 /**
737 * A wrapper for getTemplateFileName that includes calling the hook to
738 * prevent us from having to copy & paste the logic of calling the hook
739 */
740 public function getHookedTemplateFileName() {
741 $pageTemplateFile = $this->getTemplateFileName();
742 CRM_Utils_Hook::alterTemplateFile(get_class($this), $this, 'page', $pageTemplateFile);
743 return $pageTemplateFile;
744 }
745
746 /**
747 * Default extra tpl file basically just replaces .tpl with .extra.tpl
748 * i.e. we dont override
749 *
750 * @return string
751 */
752 public function overrideExtraTemplateFileName() {
753 return NULL;
754 }
755
756 /**
757 * Error reporting mechanism.
758 *
759 * @param string $message
760 * Error Message.
761 * @param int $code
762 * Error Code.
763 * @param CRM_Core_DAO $dao
764 * A data access object on which we perform a rollback if non - empty.
765 *
766 * @return void
767 */
768 public function error($message, $code = NULL, $dao = NULL) {
769 if ($dao) {
770 $dao->query('ROLLBACK');
771 }
772
773 $error = CRM_Core_Error::singleton();
774
775 $error->push($code, $message);
776 }
777
778 /**
779 * Store the variable with the value in the form scope.
780 *
781 * @param string $name
782 * Name of the variable.
783 * @param mixed $value
784 * Value of the variable.
785 *
786 * @return void
787 */
788 public function set($name, $value) {
789 $this->controller->set($name, $value);
790 }
791
792 /**
793 * Get the variable from the form scope.
794 *
795 * @param string $name
796 * Name of the variable
797 *
798 * @return mixed
799 */
800 public function get($name) {
801 return $this->controller->get($name);
802 }
803
804 /**
805 * Getter for action.
806 *
807 * @return int
808 */
809 public function getAction() {
810 return $this->_action;
811 }
812
813 /**
814 * Setter for action.
815 *
816 * @param int $action
817 * The mode we want to set the form.
818 *
819 * @return void
820 */
821 public function setAction($action) {
822 $this->_action = $action;
823 }
824
825 /**
826 * Assign value to name in template.
827 *
828 * @param string $var
829 * Name of variable.
830 * @param mixed $value
831 * Value of variable.
832 *
833 * @return void
834 */
835 public function assign($var, $value = NULL) {
836 self::$_template->assign($var, $value);
837 }
838
839 /**
840 * Assign value to name in template by reference.
841 *
842 * @param string $var
843 * Name of variable.
844 * @param mixed $value
845 * Value of varaible.
846 *
847 * @return void
848 */
849 public function assign_by_ref($var, &$value) {
850 self::$_template->assign_by_ref($var, $value);
851 }
852
853 /**
854 * Appends values to template variables.
855 *
856 * @param array|string $tpl_var the template variable name(s)
857 * @param mixed $value
858 * The value to append.
859 * @param bool $merge
860 */
861 public function append($tpl_var, $value = NULL, $merge = FALSE) {
862 self::$_template->append($tpl_var, $value, $merge);
863 }
864
865 /**
866 * Returns an array containing template variables.
867 *
868 * @param string $name
869 *
870 * @return array
871 */
872 public function get_template_vars($name = NULL) {
873 return self::$_template->get_template_vars($name);
874 }
875
876 /**
877 * @param string $name
878 * @param $title
879 * @param $values
880 * @param array $attributes
881 * @param null $separator
882 * @param bool $required
883 *
884 * @return HTML_QuickForm_group
885 */
886 public function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
887 $options = array();
888 $attributes = $attributes ? $attributes : array();
889 $allowClear = !empty($attributes['allowClear']);
890 unset($attributes['allowClear']);
891 $attributes['id_suffix'] = $name;
892 foreach ($values as $key => $var) {
893 $options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
894 }
895 $group = $this->addGroup($options, $name, $title, $separator);
896 if ($required) {
897 $this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
898 }
899 if ($allowClear) {
900 $group->setAttribute('allowClear', TRUE);
901 }
902 return $group;
903 }
904
905 /**
906 * @param int $id
907 * @param $title
908 * @param bool $allowClear
909 * @param null $required
910 * @param array $attributes
911 */
912 public function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
913 $attributes += array('id_suffix' => $id);
914 $choice = array();
915 $choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
916 $choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
917
918 $group = $this->addGroup($choice, $id, $title);
919 if ($allowClear) {
920 $group->setAttribute('allowClear', TRUE);
921 }
922 if ($required) {
923 $this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
924 }
925 }
926
927 /**
928 * @param int $id
929 * @param $title
930 * @param $values
931 * @param null $other
932 * @param null $attributes
933 * @param null $required
934 * @param null $javascriptMethod
935 * @param string $separator
936 * @param bool $flipValues
937 */
938 public function addCheckBox(
939 $id, $title, $values, $other = NULL,
940 $attributes = NULL, $required = NULL,
941 $javascriptMethod = NULL,
942 $separator = '<br />', $flipValues = FALSE
943 ) {
944 $options = array();
945
946 if ($javascriptMethod) {
947 foreach ($values as $key => $var) {
948 if (!$flipValues) {
949 $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
950 }
951 else {
952 $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
953 }
954 }
955 }
956 else {
957 foreach ($values as $key => $var) {
958 if (!$flipValues) {
959 $options[] = $this->createElement('checkbox', $var, NULL, $key);
960 }
961 else {
962 $options[] = $this->createElement('checkbox', $key, NULL, $var);
963 }
964 }
965 }
966
967 $this->addGroup($options, $id, $title, $separator);
968
969 if ($other) {
970 $this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
971 }
972
973 if ($required) {
974 $this->addRule($id,
975 ts('%1 is a required field.', array(1 => $title)),
976 'required'
977 );
978 }
979 }
980
981 public function resetValues() {
982 $data = $this->controller->container();
983 $data['values'][$this->_name] = array();
984 }
985
986 /**
987 * Simple shell that derived classes can call to add buttons to
988 * the form with a customized title for the main Submit
989 *
990 * @param string $title
991 * Title of the main button.
992 * @param string $nextType
993 * Button type for the form after processing.
994 * @param string $backType
995 * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
996 *
997 * @return void
998 */
999 public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
1000 $buttons = array();
1001 if ($backType != NULL) {
1002 $buttons[] = array(
1003 'type' => $backType,
1004 'name' => ts('Previous'),
1005 );
1006 }
1007 if ($nextType != NULL) {
1008 $nextButton = array(
1009 'type' => $nextType,
1010 'name' => $title,
1011 'isDefault' => TRUE,
1012 );
1013 if ($submitOnce) {
1014 $nextButton['js'] = array('onclick' => "return submitOnce(this,'{$this->_name}','" . ts('Processing') . "');");
1015 }
1016 $buttons[] = $nextButton;
1017 }
1018 $this->addButtons($buttons);
1019 }
1020
1021 /**
1022 * @param string $name
1023 * @param string $from
1024 * @param string $to
1025 * @param string $label
1026 * @param string $dateFormat
1027 * @param bool $required
1028 * @param bool $displayTime
1029 */
1030 public function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
1031 if ($displayTime) {
1032 $this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
1033 $this->addDateTime($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1034 }
1035 else {
1036 $this->addDate($name . $from, $label, $required, array('formatType' => $dateFormat));
1037 $this->addDate($name . $to, ts('To:'), $required, array('formatType' => $dateFormat));
1038 }
1039 }
1040
1041 /**
1042 * Classes extending CRM_Core_Form should implement this method.
1043 * @throws Exception
1044 */
1045 public function getDefaultEntity() {
1046 throw new Exception("Cannot determine default entity. The form class should implement getDefaultEntity().");
1047 }
1048
1049 /**
1050 * Adds a select based on field metadata.
1051 * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata
1052 * Perhaps a method like $form->bind($name) which would look up all metadata for named field
1053 * @param $name
1054 * Field name to go on the form.
1055 * @param array $props
1056 * Mix of html attributes and special properties, namely.
1057 * - entity (api entity name, can usually be inferred automatically from the form class)
1058 * - field (field name - only needed if different from name used on the form)
1059 * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link
1060 * - placeholder - set to NULL to disable
1061 * - multiple - bool
1062 * - context - @see CRM_Core_DAO::buildOptionsContext
1063 * @param bool $required
1064 * @throws CRM_Core_Exception
1065 * @return HTML_QuickForm_Element
1066 */
1067 public function addSelect($name, $props = array(), $required = FALSE) {
1068 if (!isset($props['entity'])) {
1069 $props['entity'] = $this->getDefaultEntity();
1070 }
1071 if (!isset($props['field'])) {
1072 $props['field'] = strrpos($name, '[') ? rtrim(substr($name, 1 + strrpos($name, '[')), ']') : $name;
1073 }
1074 // Fetch options from the api unless passed explicitly
1075 if (isset($props['options'])) {
1076 $options = $props['options'];
1077 }
1078 else {
1079 $info = civicrm_api3($props['entity'], 'getoptions', $props);
1080 $options = $info['values'];
1081 }
1082 if (!array_key_exists('placeholder', $props)) {
1083 $props['placeholder'] = $required ? ts('- select -') : CRM_Utils_Array::value('context', $props) == 'search' ? ts('- any -') : ts('- none -');
1084 }
1085 // Handle custom field
1086 if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
1087 list(, $id) = explode('_', $name);
1088 $label = isset($props['label']) ? $props['label'] : CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id);
1089 $gid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id);
1090 if (CRM_Utils_Array::value('context', $props) != 'search') {
1091 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : 'civicrm/admin/options/' . CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $gid);
1092 }
1093 }
1094 // Core field
1095 else {
1096 $info = civicrm_api3($props['entity'], 'getfields');
1097 foreach ($info['values'] as $uniqueName => $fieldSpec) {
1098 if (
1099 $uniqueName === $props['field'] ||
1100 CRM_Utils_Array::value('name', $fieldSpec) === $props['field'] ||
1101 in_array($props['field'], CRM_Utils_Array::value('api.aliases', $fieldSpec, array()))
1102 ) {
1103 break;
1104 }
1105 }
1106 $label = isset($props['label']) ? $props['label'] : $fieldSpec['title'];
1107 if (CRM_Utils_Array::value('context', $props) != 'search') {
1108 $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : $props['data-option-edit-path'] = CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec);
1109 }
1110 }
1111 $props['class'] = (isset($props['class']) ? $props['class'] . ' ' : '') . "crm-select2";
1112 $props['data-api-entity'] = $props['entity'];
1113 $props['data-api-field'] = $props['field'];
1114 CRM_Utils_Array::remove($props, 'label', 'entity', 'field', 'option_url', 'options', 'context');
1115 return $this->add('select', $name, $label, $options, $required, $props);
1116 }
1117
1118 /**
1119 * Add a widget for selecting/editing/creating/copying a profile form
1120 *
1121 * @param string $name
1122 * HTML form-element name.
1123 * @param string $label
1124 * Printable label.
1125 * @param string $allowCoreTypes
1126 * Only present a UFGroup if its group_type includes a subset of $allowCoreTypes; e.g. 'Individual', 'Activity'.
1127 * @param string $allowSubTypes
1128 * Only present a UFGroup if its group_type is compatible with $allowSubypes.
1129 * @param array $entities
1130 * @param bool $default
1131 * //CRM-15427.
1132 */
1133 public function addProfileSelector($name, $label, $allowCoreTypes, $allowSubTypes, $entities, $default = FALSE) {
1134 // Output widget
1135 // FIXME: Instead of adhoc serialization, use a single json_encode()
1136 CRM_UF_Page_ProfileEditor::registerProfileScripts();
1137 CRM_UF_Page_ProfileEditor::registerSchemas(CRM_Utils_Array::collect('entity_type', $entities));
1138 $this->add('text', $name, $label, array(
1139 'class' => 'crm-profile-selector',
1140 // Note: client treats ';;' as equivalent to \0, and ';;' works better in HTML
1141 'data-group-type' => CRM_Core_BAO_UFGroup::encodeGroupType($allowCoreTypes, $allowSubTypes, ';;'),
1142 'data-entities' => json_encode($entities),
1143 //CRM-15427
1144 'data-default' => $default,
1145 ));
1146 }
1147
1148 /**
1149 * @param string $name
1150 * @param $label
1151 * @param $attributes
1152 * @param bool $forceTextarea
1153 */
1154 public function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
1155 // 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
1156 // 2. Based on the option, initialise proper editor
1157 $editorID = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
1158 'editor_id'
1159 );
1160 $editor = strtolower(CRM_Utils_Array::value($editorID,
1161 CRM_Core_OptionGroup::values('wysiwyg_editor')
1162 ));
1163 if (!$editor || $forceTextarea) {
1164 $editor = 'textarea';
1165 }
1166 if ($editor == 'joomla default editor') {
1167 $editor = 'joomlaeditor';
1168 }
1169
1170 if ($editor == 'drupal default editor') {
1171 $editor = 'drupalwysiwyg';
1172 }
1173
1174 //lets add the editor as a attribute
1175 $attributes['editor'] = $editor;
1176
1177 $this->addElement($editor, $name, $label, $attributes);
1178 $this->assign('editor', $editor);
1179
1180 // include wysiwyg editor js files
1181 // FIXME: This code does not make any sense
1182 $includeWysiwygEditor = FALSE;
1183 $includeWysiwygEditor = $this->get('includeWysiwygEditor');
1184 if (!$includeWysiwygEditor) {
1185 $includeWysiwygEditor = TRUE;
1186 $this->set('includeWysiwygEditor', $includeWysiwygEditor);
1187 }
1188
1189 $this->assign('includeWysiwygEditor', $includeWysiwygEditor);
1190 }
1191
1192 /**
1193 * @param int $id
1194 * @param $title
1195 * @param null $required
1196 * @param null $extra
1197 */
1198 public function addCountry($id, $title, $required = NULL, $extra = NULL) {
1199 $this->addElement('select', $id, $title,
1200 array(
1201 '' => ts('- select -'),
1202 ) + CRM_Core_PseudoConstant::country(), $extra
1203 );
1204 if ($required) {
1205 $this->addRule($id, ts('Please select %1', array(1 => $title)), 'required');
1206 }
1207 }
1208
1209 /**
1210 * @param string $name
1211 * @param $label
1212 * @param $options
1213 * @param $attributes
1214 * @param null $required
1215 * @param null $javascriptMethod
1216 */
1217 public function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
1218
1219 $this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
1220
1221 if ($required) {
1222 $this->addRule($name . '_id', ts('Please select %1', array(1 => $label)), 'required');
1223 }
1224 }
1225
1226 /**
1227 * @return null
1228 */
1229 public function getRootTitle() {
1230 return NULL;
1231 }
1232
1233 /**
1234 * @return string
1235 */
1236 public function getCompleteTitle() {
1237 return $this->getRootTitle() . $this->getTitle();
1238 }
1239
1240 /**
1241 * @return CRM_Core_Smarty
1242 */
1243 public static function &getTemplate() {
1244 return self::$_template;
1245 }
1246
1247 /**
1248 * @param $elementName
1249 */
1250 public function addUploadElement($elementName) {
1251 $uploadNames = $this->get('uploadNames');
1252 if (!$uploadNames) {
1253 $uploadNames = array();
1254 }
1255 if (is_array($elementName)) {
1256 foreach ($elementName as $name) {
1257 if (!in_array($name, $uploadNames)) {
1258 $uploadNames[] = $name;
1259 }
1260 }
1261 }
1262 else {
1263 if (!in_array($elementName, $uploadNames)) {
1264 $uploadNames[] = $elementName;
1265 }
1266 }
1267 $this->set('uploadNames', $uploadNames);
1268
1269 $config = CRM_Core_Config::singleton();
1270 if (!empty($uploadNames)) {
1271 $this->controller->addUploadAction($config->customFileUploadDir, $uploadNames);
1272 }
1273 }
1274
1275 /**
1276 * @return string
1277 */
1278 public function buttonType() {
1279 $uploadNames = $this->get('uploadNames');
1280 $buttonType = (is_array($uploadNames) && !empty($uploadNames)) ? 'upload' : 'next';
1281 $this->assign('buttonType', $buttonType);
1282 return $buttonType;
1283 }
1284
1285 /**
1286 * @param $name
1287 *
1288 * @return null
1289 */
1290 public function getVar($name) {
1291 return isset($this->$name) ? $this->$name : NULL;
1292 }
1293
1294 /**
1295 * @param $name
1296 * @param $value
1297 */
1298 public function setVar($name, $value) {
1299 $this->$name = $value;
1300 }
1301
1302 /**
1303 * Add date.
1304 *
1305 * @code
1306 * // if you need time
1307 * $attributes = array(
1308 * 'addTime' => true,
1309 * 'formatType' => 'relative' or 'birth' etc check advanced date settings
1310 * );
1311 * @endcode
1312 *
1313 * @param string $name
1314 * Name of the element.
1315 * @param string $label
1316 * Label of the element.
1317 * @param bool $required
1318 * True if required.
1319 * @param array $attributes
1320 * Key / value pair.
1321 */
1322 public function addDate($name, $label, $required = FALSE, $attributes = NULL) {
1323 if (!empty($attributes['formatType'])) {
1324 // get actual format
1325 $params = array('name' => $attributes['formatType']);
1326 $values = array();
1327
1328 // cache date information
1329 static $dateFormat;
1330 $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
1331 if (empty($dateFormat[$key])) {
1332 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
1333 $dateFormat[$key] = $values;
1334 }
1335 else {
1336 $values = $dateFormat[$key];
1337 }
1338
1339 if ($values['date_format']) {
1340 $attributes['format'] = $values['date_format'];
1341 }
1342
1343 if (!empty($values['time_format'])) {
1344 $attributes['timeFormat'] = $values['time_format'];
1345 }
1346 $attributes['startOffset'] = $values['start'];
1347 $attributes['endOffset'] = $values['end'];
1348 }
1349
1350 $config = CRM_Core_Config::singleton();
1351 if (empty($attributes['format'])) {
1352 $attributes['format'] = $config->dateInputFormat;
1353 }
1354
1355 if (!isset($attributes['startOffset'])) {
1356 $attributes['startOffset'] = 10;
1357 }
1358
1359 if (!isset($attributes['endOffset'])) {
1360 $attributes['endOffset'] = 10;
1361 }
1362
1363 $this->add('text', $name, $label, $attributes);
1364
1365 if (!empty($attributes['addTime']) || !empty($attributes['timeFormat'])) {
1366
1367 if (!isset($attributes['timeFormat'])) {
1368 $timeFormat = $config->timeInputFormat;
1369 }
1370 else {
1371 $timeFormat = $attributes['timeFormat'];
1372 }
1373
1374 // 1 - 12 hours and 2 - 24 hours, but for jquery widget it is 0 and 1 respectively
1375 if ($timeFormat) {
1376 $show24Hours = TRUE;
1377 if ($timeFormat == 1) {
1378 $show24Hours = FALSE;
1379 }
1380
1381 //CRM-6664 -we are having time element name
1382 //in either flat string or an array format.
1383 $elementName = $name . '_time';
1384 if (substr($name, -1) == ']') {
1385 $elementName = substr($name, 0, strlen($name) - 1) . '_time]';
1386 }
1387
1388 $this->add('text', $elementName, ts('Time'), array('timeFormat' => $show24Hours));
1389 }
1390 }
1391
1392 if ($required) {
1393 $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
1394 if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
1395 $this->addRule($elementName, ts('Please enter a time.'), 'required');
1396 }
1397 }
1398 }
1399
1400 /**
1401 * Function that will add date and time.
1402 */
1403 public function addDateTime($name, $label, $required = FALSE, $attributes = NULL) {
1404 $addTime = array('addTime' => TRUE);
1405 if (is_array($attributes)) {
1406 $attributes = array_merge($attributes, $addTime);
1407 }
1408 else {
1409 $attributes = $addTime;
1410 }
1411
1412 $this->addDate($name, $label, $required, $attributes);
1413 }
1414
1415 /**
1416 * Add a currency and money element to the form.
1417 */
1418 public function addMoney(
1419 $name,
1420 $label,
1421 $required = FALSE,
1422 $attributes = NULL,
1423 $addCurrency = TRUE,
1424 $currencyName = 'currency',
1425 $defaultCurrency = NULL,
1426 $freezeCurrency = FALSE
1427 ) {
1428 $element = $this->add('text', $name, $label, $attributes, $required);
1429 $this->addRule($name, ts('Please enter a valid amount.'), 'money');
1430
1431 if ($addCurrency) {
1432 $ele = $this->addCurrency($currencyName, NULL, TRUE, $defaultCurrency, $freezeCurrency);
1433 }
1434
1435 return $element;
1436 }
1437
1438 /**
1439 * Add currency element to the form.
1440 */
1441 public function addCurrency(
1442 $name = 'currency',
1443 $label = NULL,
1444 $required = TRUE,
1445 $defaultCurrency = NULL,
1446 $freezeCurrency = FALSE
1447 ) {
1448 $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
1449 $options = array('class' => 'crm-select2 eight');
1450 if (!$required) {
1451 $currencies = array('' => '') + $currencies;
1452 $options['placeholder'] = ts('- none -');
1453 }
1454 $ele = $this->add('select', $name, $label, $currencies, $required, $options);
1455 if ($freezeCurrency) {
1456 $ele->freeze();
1457 }
1458 if (!$defaultCurrency) {
1459 $config = CRM_Core_Config::singleton();
1460 $defaultCurrency = $config->defaultCurrency;
1461 }
1462 $this->setDefaults(array($name => $defaultCurrency));
1463 }
1464
1465 /**
1466 * Create a single or multiple entity ref field.
1467 * @param string $name
1468 * @param string $label
1469 * @param array $props
1470 * Mix of html and widget properties, including:.
1471 * - select - params to give to select2 widget
1472 * - entity - defaults to contact
1473 * - create - can the user create a new entity on-the-fly?
1474 * Set to TRUE if entity is contact and you want the default profiles,
1475 * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
1476 * note that permissions are checked automatically
1477 * - api - array of settings for the getlist api wrapper
1478 * note that it accepts a 'params' setting which will be passed to the underlying api
1479 * - placeholder - string
1480 * - multiple - bool
1481 * - class, etc. - other html properties
1482 * @param bool $required
1483 *
1484 * @return HTML_QuickForm_Element
1485 */
1486 public function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
1487 require_once "api/api.php";
1488 $config = CRM_Core_Config::singleton();
1489 // Default properties
1490 $props['api'] = CRM_Utils_Array::value('api', $props, array());
1491 $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array::value('entity', $props, 'contact'));
1492 $props['class'] = ltrim(CRM_Utils_Array::value('class', $props, '') . ' crm-form-entityref');
1493
1494 if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission::check('edit all contacts') || CRM_Core_Permission::check('add contacts'))) {
1495 unset($props['create']);
1496 }
1497
1498 $props['placeholder'] = CRM_Utils_Array::value('placeholder', $props, $required ? ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
1499
1500 $defaults = array();
1501 if (!empty($props['multiple'])) {
1502 $defaults['multiple'] = TRUE;
1503 }
1504 $props['select'] = CRM_Utils_Array::value('select', $props, array()) + $defaults;
1505
1506 $this->formatReferenceFieldAttributes($props);
1507 return $this->add('text', $name, $label, $props, $required);
1508 }
1509
1510 /**
1511 * @param $props
1512 */
1513 private function formatReferenceFieldAttributes(&$props) {
1514 $props['data-select-params'] = json_encode($props['select']);
1515 $props['data-api-params'] = $props['api'] ? json_encode($props['api']) : NULL;
1516 $props['data-api-entity'] = $props['entity'];
1517 if (!empty($props['create'])) {
1518 $props['data-create-links'] = json_encode($props['create']);
1519 }
1520 CRM_Utils_Array::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
1521 }
1522
1523 /**
1524 * Convert all date fields within the params to mysql date ready for the
1525 * BAO layer. In this case fields are checked against the $_datefields defined for the form
1526 * and if time is defined it is incorporated
1527 *
1528 * @param array $params
1529 * Input params from the form.
1530 *
1531 * @todo it would probably be better to work on $this->_params than a passed array
1532 * @todo standardise the format which dates are passed to the BAO layer in & remove date
1533 * handling from BAO
1534 */
1535 public function convertDateFieldsToMySQL(&$params) {
1536 foreach ($this->_dateFields as $fieldName => $specs) {
1537 if (!empty($params[$fieldName])) {
1538 $params[$fieldName] = CRM_Utils_Date::isoToMysql(
1539 CRM_Utils_Date::processDate(
1540 $params[$fieldName],
1541 CRM_Utils_Array::value("{$fieldName}_time", $params), TRUE)
1542 );
1543 }
1544 else {
1545 if (isset($specs['default'])) {
1546 $params[$fieldName] = date('YmdHis', strtotime($specs['default']));
1547 }
1548 }
1549 }
1550 }
1551
1552 /**
1553 * @param $elementName
1554 */
1555 public function removeFileRequiredRules($elementName) {
1556 $this->_required = array_diff($this->_required, array($elementName));
1557 if (isset($this->_rules[$elementName])) {
1558 foreach ($this->_rules[$elementName] as $index => $ruleInfo) {
1559 if ($ruleInfo['type'] == 'uploadedfile') {
1560 unset($this->_rules[$elementName][$index]);
1561 }
1562 }
1563 if (empty($this->_rules[$elementName])) {
1564 unset($this->_rules[$elementName]);
1565 }
1566 }
1567 }
1568
1569 /**
1570 * Function that can be defined in Form to override or.
1571 * perform specific action on cancel action
1572 */
1573 public function cancelAction() {
1574 }
1575
1576 /**
1577 * Helper function to verify that required fields have been filled.
1578 * Typically called within the scope of a FormRule function
1579 */
1580 public static function validateMandatoryFields($fields, $values, &$errors) {
1581 foreach ($fields as $name => $fld) {
1582 if (!empty($fld['is_required']) && CRM_Utils_System::isNull(CRM_Utils_Array::value($name, $values))) {
1583 $errors[$name] = ts('%1 is a required field.', array(1 => $fld['title']));
1584 }
1585 }
1586 }
1587
1588 /**
1589 * Get contact if for a form object. Prioritise
1590 * - cid in URL if 0 (on behalf on someoneelse)
1591 * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
1592 * - logged in user id if it matches the one in the cid in the URL
1593 * - contact id validated from a checksum from a checksum
1594 * - cid from the url if the caller has ACL permission to view
1595 * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
1596 *
1597 * @return NULL|int
1598 */
1599 public function getContactID() {
1600 $tempID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
1601 if (isset($this->_params) && isset($this->_params['select_contact_id'])) {
1602 $tempID = $this->_params['select_contact_id'];
1603 }
1604 if (isset($this->_params, $this->_params[0]) && !empty($this->_params[0]['select_contact_id'])) {
1605 // event form stores as an indexed array, contribution form not so much...
1606 $tempID = $this->_params[0]['select_contact_id'];
1607 }
1608
1609 // force to ignore the authenticated user
1610 if ($tempID === '0' || $tempID === 0) {
1611 // we set the cid on the form so that this will be retained for the Confirm page
1612 // in the multi-page form & prevent us returning the $userID when this is called
1613 // from that page
1614 // we don't really need to set it when $tempID is set because the params have that stored
1615 $this->set('cid', 0);
1616 return $tempID;
1617 }
1618
1619 $userID = $this->getLoggedInUserContactID();
1620
1621 if ($tempID == $userID) {
1622 return $userID;
1623 }
1624
1625 //check if this is a checksum authentication
1626 $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this);
1627 if ($userChecksum) {
1628 //check for anonymous user.
1629 $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($tempID, $userChecksum);
1630 if ($validUser) {
1631 return $tempID;
1632 }
1633 }
1634 // check if user has permission, CRM-12062
1635 elseif ($tempID && CRM_Contact_BAO_Contact_Permission::allow($tempID)) {
1636 return $tempID;
1637 }
1638
1639 return $userID;
1640 }
1641
1642 /**
1643 * Get the contact id of the logged in user.
1644 */
1645 public function getLoggedInUserContactID() {
1646 // check if the user is logged in and has a contact ID
1647 $session = CRM_Core_Session::singleton();
1648 return $session->get('userID');
1649 }
1650
1651 /**
1652 * Add autoselector field -if user has permission to view contacts
1653 * If adding this to a form you also need to add to the tpl e.g
1654 *
1655 * {if !empty($selectable)}
1656 * <div class="crm-summary-row">
1657 * <div class="crm-label">{$form.select_contact.label}</div>
1658 * <div class="crm-content">
1659 * {$form.select_contact.html}
1660 * </div>
1661 * </div>
1662 * {/if}
1663 *
1664 * @param array $profiles
1665 * Ids of profiles that are on the form (to be autofilled).
1666 * @param array $autoCompleteField
1667 *
1668 * - name_field
1669 * - id_field
1670 * - url (for ajax lookup)
1671 *
1672 * @todo add data attributes so we can deal with multiple instances on a form
1673 */
1674 public function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
1675 $autoCompleteField = array_merge(array(
1676 'id_field' => 'select_contact_id',
1677 'placeholder' => ts('Select someone else ...'),
1678 'show_hide' => TRUE,
1679 'api' => array('params' => array('contact_type' => 'Individual')),
1680 ), $autoCompleteField);
1681
1682 if ($this->canUseAjaxContactLookups()) {
1683 $this->assign('selectable', $autoCompleteField['id_field']);
1684 $this->addEntityRef($autoCompleteField['id_field'], NULL, array(
1685 'placeholder' => $autoCompleteField['placeholder'],
1686 'api' => $autoCompleteField['api'],
1687 ));
1688
1689 CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js', 1, 'html-header')
1690 ->addSetting(array(
1691 'form' => array('autocompletes' => $autoCompleteField),
1692 'ids' => array('profile' => $profiles),
1693 ));
1694 }
1695 }
1696
1697 /**
1698 */
1699 public function canUseAjaxContactLookups() {
1700 if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
1701 CRM_Core_Permission::check(array(array('access AJAX API', 'access CiviCRM')))
1702 ) {
1703 return TRUE;
1704 }
1705 }
1706
1707 /**
1708 * Add the options appropriate to cid = zero - ie. autocomplete
1709 *
1710 * @todo there is considerable code duplication between the contribution forms & event forms. It is apparent
1711 * that small pieces of duplication are not being refactored into separate functions because their only shared parent
1712 * is this form. Inserting a class FrontEndForm.php between the contribution & event & this class would allow functions like this
1713 * and a dozen other small ones to be refactored into a shared parent with the reduction of much code duplication
1714 *
1715 * @param $onlinePaymentProcessorEnabled
1716 */
1717 public function addCIDZeroOptions($onlinePaymentProcessorEnabled) {
1718 $this->assign('nocid', TRUE);
1719 $profiles = array();
1720 if ($this->_values['custom_pre_id']) {
1721 $profiles[] = $this->_values['custom_pre_id'];
1722 }
1723 if ($this->_values['custom_post_id']) {
1724 $profiles = array_merge($profiles, (array) $this->_values['custom_post_id']);
1725 }
1726 if ($onlinePaymentProcessorEnabled) {
1727 $profiles[] = 'billing';
1728 }
1729 if (!empty($this->_values)) {
1730 $this->addAutoSelector($profiles);
1731 }
1732 }
1733
1734 /**
1735 * Set default values on form for given contact (or no contact defaults)
1736 *
1737 * @param mixed $profile_id
1738 * (can be id, or profile name).
1739 * @param int $contactID
1740 *
1741 * @return array
1742 */
1743 public function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
1744 try {
1745 $defaults = civicrm_api3('profile', 'getsingle', array(
1746 'profile_id' => (array) $profile_id,
1747 'contact_id' => $contactID,
1748 ));
1749 return $defaults;
1750 }
1751 catch (Exception $e) {
1752 // the try catch block gives us silent failure -not 100% sure this is a good idea
1753 // as silent failures are often worse than noisy ones
1754 return array();
1755 }
1756 }
1757
1758 /**
1759 * Sets form attribute.
1760 * @see CRM.loadForm
1761 */
1762 public function preventAjaxSubmit() {
1763 $this->setAttribute('data-no-ajax-submit', 'true');
1764 }
1765
1766 /**
1767 * Sets form attribute.
1768 * @see CRM.loadForm
1769 */
1770 public function allowAjaxSubmit() {
1771 $this->removeAttribute('data-no-ajax-submit');
1772 }
1773
1774 /**
1775 * Sets page title based on entity and action.
1776 * @param string $entityLabel
1777 */
1778 public function setPageTitle($entityLabel) {
1779 switch ($this->_action) {
1780 case CRM_Core_Action::ADD:
1781 CRM_Utils_System::setTitle(ts('New %1', array(1 => $entityLabel)));
1782 break;
1783
1784 case CRM_Core_Action::UPDATE:
1785 CRM_Utils_System::setTitle(ts('Edit %1', array(1 => $entityLabel)));
1786 break;
1787
1788 case CRM_Core_Action::VIEW:
1789 case CRM_Core_Action::PREVIEW:
1790 CRM_Utils_System::setTitle(ts('View %1', array(1 => $entityLabel)));
1791 break;
1792
1793 case CRM_Core_Action::DELETE:
1794 CRM_Utils_System::setTitle(ts('Delete %1', array(1 => $entityLabel)));
1795 break;
1796 }
1797 }
1798
1799 /**
1800 * Create a chain-select target field. All settings are optional; the defaults usually work.
1801 *
1802 * @param string $elementName
1803 * @param array $settings
1804 *
1805 * @return HTML_QuickForm_Element
1806 */
1807 public function addChainSelect($elementName, $settings = array()) {
1808 $props = $settings += array(
1809 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array(
1810 'country',
1811 'Country',
1812 'state_province',
1813 'StateProvince',
1814 ), $elementName),
1815 'data-callback' => strpos($elementName, 'rovince') ? 'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
1816 'label' => strpos($elementName, 'rovince') ? ts('State/Province') : ts('County'),
1817 'data-empty-prompt' => strpos($elementName, 'rovince') ? ts('Choose country first') : ts('Choose state first'),
1818 'data-none-prompt' => ts('- N/A -'),
1819 'multiple' => FALSE,
1820 'required' => FALSE,
1821 'placeholder' => empty($settings['required']) ? ts('- none -') : ts('- select -'),
1822 );
1823 CRM_Utils_Array::remove($props, 'label', 'required', 'control_field');
1824 $props['class'] = (empty($props['class']) ? '' : "{$props['class']} ") . 'crm-select2';
1825 $props['data-select-prompt'] = $props['placeholder'];
1826 $props['data-name'] = $elementName;
1827
1828 $this->_chainSelectFields[$settings['control_field']] = $elementName;
1829
1830 // Passing NULL instead of an array of options
1831 // CRM-15225 - normally QF will reject any selected values that are not part of the field's options, but due to a
1832 // quirk in our patched version of HTML_QuickForm_select, this doesn't happen if the options are NULL
1833 // which seems a bit dirty but it allows our dynamically-popuplated select element to function as expected.
1834 return $this->add('select', $elementName, $settings['label'], NULL, $settings['required'], $props);
1835 }
1836
1837 /**
1838 * Set options and attributes for chain select fields based on the controlling field's value
1839 */
1840 private function preProcessChainSelectFields() {
1841 foreach ($this->_chainSelectFields as $control => $target) {
1842 // The 'target' might get missing if extensions do removeElement() in a form hook.
1843 if ($this->elementExists($target)) {
1844 $targetField = $this->getElement($target);
1845 $targetType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'county' : 'stateProvince';
1846 $options = array();
1847 // If the control field is on the form, setup chain-select and dynamically populate options
1848 if ($this->elementExists($control)) {
1849 $controlField = $this->getElement($control);
1850 $controlType = $targetType == 'county' ? 'stateProvince' : 'country';
1851
1852 $targetField->setAttribute('class', $targetField->getAttribute('class') . ' crm-chain-select-target');
1853
1854 $css = (string) $controlField->getAttribute('class');
1855 $controlField->updateAttributes(array(
1856 'class' => ($css ? "$css " : 'crm-select2 ') . 'crm-chain-select-control',
1857 'data-target' => $target,
1858 ));
1859 $controlValue = $controlField->getValue();
1860 if ($controlValue) {
1861 $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
1862 if (!$options) {
1863 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
1864 }
1865 }
1866 else {
1867 $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
1868 $targetField->setAttribute('disabled', 'disabled');
1869 }
1870 }
1871 // Control field not present - fall back to loading default options
1872 else {
1873 $options = CRM_Core_PseudoConstant::$targetType();
1874 }
1875 if (!$targetField->getAttribute('multiple')) {
1876 $options = array('' => $targetField->getAttribute('placeholder')) + $options;
1877 $targetField->removeAttribute('placeholder');
1878 }
1879 $targetField->_options = array();
1880 $targetField->loadArray($options);
1881 }
1882 }
1883 }
1884
1885 /**
1886 * Validate country / state / county match and suppress unwanted "required" errors
1887 */
1888 private function validateChainSelectFields() {
1889 foreach ($this->_chainSelectFields as $control => $target) {
1890 if ($this->elementExists($control) && $this->elementExists($target)) {
1891 $controlValue = (array) $this->getElementValue($control);
1892 $targetField = $this->getElement($target);
1893 $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'stateProvince' : 'country';
1894 $targetValue = array_filter((array) $targetField->getValue());
1895 if ($targetValue || $this->getElementError($target)) {
1896 $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
1897 if ($targetValue) {
1898 if (!array_intersect($targetValue, array_keys($options))) {
1899 $this->setElementError($target, $controlType == 'country' ? ts('State/Province does not match the selected Country') : ts('County does not match the selected State/Province'));
1900 }
1901 } // Suppress "required" error for field if it has no options
1902 elseif (!$options) {
1903 $this->setElementError($target, NULL);
1904 }
1905 }
1906 }
1907 }
1908 }
1909
1910 }