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