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