Merge pull request #15699 from mattwire/participant_cleanup_completeOrderPBRef
[civicrm-core.git] / CRM / Pledge / Form / Pledge.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2020 |
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 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2020
32 */
33
34 /**
35 * This class generates form components for processing a pledge
36 */
37 class CRM_Pledge_Form_Pledge extends CRM_Core_Form {
38 public $_action;
39
40 /**
41 * The id of the pledge that we are proceessing.
42 *
43 * @var int
44 */
45 public $_id;
46
47 /**
48 * The id of the contact associated with this pledge.
49 *
50 * @var int
51 */
52 public $_contactID;
53
54 /**
55 * The Pledge values if an existing pledge.
56 * @var array
57 */
58 public $_values;
59
60 /**
61 * The Pledge frequency Units.
62 * @var array
63 */
64 public $_freqUnits;
65
66 /**
67 * Is current pledge pending.
68 * @var bool
69 */
70 public $_isPending = FALSE;
71
72 /**
73 * Set variables up before form is built.
74 */
75 public function preProcess() {
76 $this->_contactID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
77 $this->_action = CRM_Utils_Request::retrieve('action', 'String',
78 $this, FALSE, 'add'
79 );
80 $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this);
81 $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this);
82
83 // check for action permissions.
84 if (!CRM_Core_Permission::checkActionPermission('CiviPledge', $this->_action)) {
85 CRM_Core_Error::fatal(ts('You do not have permission to access this page.'));
86 }
87
88 $this->assign('action', $this->_action);
89 $this->assign('context', $this->_context);
90 if ($this->_action & CRM_Core_Action::DELETE) {
91 return;
92 }
93
94 $this->userDisplayName = $this->userEmail = NULL;
95 if ($this->_contactID) {
96 list($this->userDisplayName,
97 $this->userEmail
98 ) = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_contactID);
99 }
100
101 $this->setPageTitle(ts('Pledge'));
102
103 // build custom data
104 CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'Pledge', $this->_id);
105 $this->_values = [];
106 // current pledge id
107 if ($this->_id) {
108 // get the contribution id
109 $this->_contributionID = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment',
110 $this->_id, 'contribution_id', 'pledge_id'
111 );
112 $params = ['id' => $this->_id];
113 CRM_Pledge_BAO_Pledge::getValues($params, $this->_values);
114
115 $this->_isPending = (CRM_Pledge_BAO_Pledge::pledgeHasFinancialTransactions($this->_id, CRM_Utils_Array::value('status_id', $this->_values))) ? FALSE : TRUE;
116 }
117
118 // get the pledge frequency units.
119 $this->_freqUnits = CRM_Core_OptionGroup::values('recur_frequency_units');
120
121 $this->_fromEmails = CRM_Core_BAO_Email::getFromEmail();
122 }
123
124 /**
125 * Set default values for the form.
126 * The default values are retrieved from the database.
127 */
128 public function setDefaultValues() {
129 $defaults = $this->_values;
130
131 if ($this->_action & CRM_Core_Action::DELETE) {
132 return $defaults;
133 }
134
135 if (!empty($defaults['is_test'])) {
136 $this->assign('is_test', TRUE);
137 }
138
139 if ($this->_id) {
140 // check is this pledge pending.
141 // fix the display of the monetary value, CRM-4038.
142 if ($this->_isPending) {
143 $defaults['eachPaymentAmount'] = $this->_values['amount'] / $this->_values['installments'];
144 $defaults['eachPaymentAmount'] = CRM_Utils_Money::format($defaults['eachPaymentAmount'], NULL, '%a');
145 }
146
147 // fix the display of the monetary value, CRM-4038
148 if (isset($this->_values['amount'])) {
149 $defaults['amount'] = CRM_Utils_Money::format($this->_values['amount'], NULL, '%a');
150 }
151 $this->assign('amount', $this->_values['amount']);
152 $this->assign('installments', $defaults['installments']);
153 }
154 else {
155 if ($this->_contactID) {
156 $defaults['contact_id'] = $this->_contactID;
157 }
158 // default values.
159 $defaults['create_date'] = date('Y-m-d');
160 $defaults['start_date'] = date('Y-m-d');
161 $defaults['installments'] = 12;
162 $defaults['frequency_interval'] = 1;
163 $defaults['frequency_day'] = 1;
164 $defaults['initial_reminder_day'] = 5;
165 $defaults['max_reminders'] = 1;
166 $defaults['additional_reminder_day'] = 5;
167 $defaults['frequency_unit'] = array_search('month', $this->_freqUnits);
168 $defaults['financial_type_id'] = array_search('Donation', CRM_Contribute_PseudoConstant::financialType());
169 }
170
171 $pledgeStatus = CRM_Pledge_BAO_Pledge::buildOptions('status_id');
172 $pledgeStatusNames = CRM_Core_OptionGroup::values('pledge_status',
173 FALSE, FALSE, FALSE, NULL, 'name', TRUE
174 );
175 // get default status label (pending)
176 $defaultPledgeStatus = CRM_Utils_Array::value(array_search('Pending', $pledgeStatusNames),
177 $pledgeStatus
178 );
179
180 // assign status.
181 $this->assign('status', CRM_Utils_Array::value(CRM_Utils_Array::value('status_id', $this->_values),
182 $pledgeStatus,
183 $defaultPledgeStatus
184 ));
185
186 if (isset($this->userEmail)) {
187 $this->assign('email', $this->userEmail);
188 }
189
190 // custom data set defaults
191 $defaults += CRM_Custom_Form_CustomData::setDefaultValues($this);
192
193 return $defaults;
194 }
195
196 /**
197 * Build the form object.
198 */
199 public function buildQuickForm() {
200 if ($this->_action & CRM_Core_Action::DELETE) {
201 $this->addButtons([
202 [
203 'type' => 'next',
204 'name' => ts('Delete'),
205 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
206 'isDefault' => TRUE,
207 ],
208 [
209 'type' => 'cancel',
210 'name' => ts('Cancel'),
211 ],
212 ]);
213 return;
214 }
215
216 $contactField = $this->addEntityRef('contact_id', ts('Contact'), ['create' => TRUE, 'api' => ['extra' => ['email']]], TRUE);
217 if ($this->_context != 'standalone') {
218 $contactField->freeze();
219 }
220
221 $showAdditionalInfo = FALSE;
222 $this->_formType = CRM_Utils_Array::value('formType', $_GET);
223
224 $defaults = [];
225
226 $paneNames = [
227 'Payment Reminders' => 'PaymentReminders',
228 ];
229 foreach ($paneNames as $name => $type) {
230 $urlParams = "snippet=4&formType={$type}";
231 $allPanes[$name] = [
232 'url' => CRM_Utils_System::url('civicrm/contact/view/pledge', $urlParams),
233 'open' => 'false',
234 'id' => $type,
235 ];
236 // see if we need to include this paneName in the current form
237 if ($this->_formType == $type || !empty($_POST["hidden_{$type}"]) ||
238 CRM_Utils_Array::value("hidden_{$type}", $defaults)
239 ) {
240 $showAdditionalInfo = TRUE;
241 $allPanes[$name]['open'] = 'true';
242 }
243 $fnName = "build{$type}";
244 CRM_Contribute_Form_AdditionalInfo::$fnName($this);
245 }
246
247 $this->assign('allPanes', $allPanes);
248 $this->assign('showAdditionalInfo', $showAdditionalInfo);
249
250 if ($this->_formType) {
251 $this->assign('formType', $this->_formType);
252 return;
253 }
254
255 $this->applyFilter('__ALL__', 'trim');
256
257 // pledge fields.
258 $attributes = CRM_Core_DAO::getAttribute('CRM_Pledge_DAO_Pledge');
259
260 $this->assign('isPending', $this->_isPending);
261
262 $js = [
263 'onblur' => "calculatedPaymentAmount( );",
264 'onkeyup' => "calculatedPaymentAmount( );",
265 ];
266
267 $amount = $this->addMoney('amount', ts('Total Pledge Amount'), TRUE,
268 array_merge($attributes['pledge_amount'], $js), TRUE,
269 'currency', NULL, $this->_id && !$this->_isPending
270 );
271
272 $installments = &$this->add('text', 'installments', ts('To be paid in'),
273 array_merge($attributes['installments'], $js), TRUE
274 );
275 $this->addRule('installments', ts('Please enter a valid number of installments.'), 'positiveInteger');
276
277 $frequencyInterval = $this->add('number', 'frequency_interval', ts('every'),
278 $attributes['pledge_frequency_interval'], TRUE
279 );
280 $this->addRule('frequency_interval', ts('Please enter a number for frequency (e.g. every "3" months).'), 'positiveInteger');
281
282 // Fix frequency unit display for use with frequency_interval
283 $freqUnitsDisplay = [];
284 foreach ($this->_freqUnits as $val => $label) {
285 $freqUnitsDisplay[$val] = ts('%1(s)', [1 => $label]);
286 }
287 $frequencyUnit = $this->add('select', 'frequency_unit',
288 ts('Frequency'),
289 ['' => ts('- select -')] + $freqUnitsDisplay,
290 TRUE
291 );
292
293 $frequencyDay = $this->add('number', 'frequency_day', ts('Payments are due on the'), $attributes['frequency_day'], TRUE);
294 $this->addRule('frequency_day', ts('Please enter a valid payment due day.'), 'positiveInteger');
295
296 $this->add('text', 'eachPaymentAmount', ts('each'), [
297 'size' => 10,
298 'style' => "background-color:#EBECE4",
299 // WTF, preserved because its inexplicable
300 0 => 'READONLY',
301 ]);
302
303 // add various dates
304 $createDate = $this->add('datepicker', 'create_date', ts('Pledge Made'), [], TRUE, ['time' => FALSE]);
305 $startDate = $this->add('datepicker', 'start_date', ts('Payments Start'), [], TRUE, ['time' => FALSE]);
306
307 if (!empty($this->_values['currency'])) {
308 $this->assign('currency', $this->_values['currency']);
309 }
310 elseif (!empty($this->_submitValues['currency'])) {
311 $this->assign('currency', $this->_submitValues['currency']);
312 }
313
314 if ($this->_id && !$this->_isPending) {
315 $amount->freeze();
316 $installments->freeze();
317 $createDate->freeze();
318 $startDate->freeze();
319 $frequencyInterval->freeze();
320 $frequencyUnit->freeze();
321 $frequencyDay->freeze();
322 $eachPaymentAmount = $this->_values['original_installment_amount'];
323 $this->assign('eachPaymentAmount', $eachPaymentAmount);
324 }
325
326 if (CRM_Utils_Array::value('status_id', $this->_values) !=
327 CRM_Core_PseudoConstant::getKey('CRM_Pledge_BAO_Pledge', 'status_id', 'Cancelled')
328 ) {
329
330 $this->addElement('checkbox', 'is_acknowledge', ts('Send Acknowledgment?'), NULL,
331 ['onclick' => "showHideByValue( 'is_acknowledge', '', 'acknowledgeDate', 'table-row', 'radio', true); showHideByValue( 'is_acknowledge', '', 'fromEmail', 'table-row', 'radio', false );"]
332 );
333
334 $this->add('select', 'from_email_address', ts('Receipt From'), $this->_fromEmails);
335 }
336
337 $this->add('datepicker', 'acknowledge_date', ts('Acknowledgment Date'), [], FALSE, ['time' => FALSE]);
338
339 $this->add('select', 'financial_type_id',
340 ts('Financial Type'),
341 ['' => ts('- select -')] + CRM_Contribute_PseudoConstant::financialType(),
342 TRUE
343 );
344
345 // CRM-7362 --add campaigns.
346 CRM_Campaign_BAO_Campaign::addCampaign($this, CRM_Utils_Array::value('campaign_id', $this->_values));
347
348 $pageIds = [];
349 CRM_Core_DAO::commonRetrieveAll('CRM_Pledge_DAO_PledgeBlock', 'entity_table',
350 'civicrm_contribution_page', $pageIds, ['entity_id']
351 );
352 $pages = CRM_Contribute_PseudoConstant::contributionPage();
353 $pledgePages = [];
354 foreach ($pageIds as $key => $value) {
355 $pledgePages[$value['entity_id']] = $pages[$value['entity_id']];
356 }
357 $this->add('select', 'contribution_page_id', ts('Self-service Payments Page'),
358 ['' => ts('- select -')] + $pledgePages
359 );
360
361 $mailingInfo = Civi::settings()->get('mailing_backend');
362 $this->assign('outBound_option', $mailingInfo['outBound_option']);
363
364 // build custom data
365 CRM_Custom_Form_CustomData::buildQuickForm($this);
366
367 // make this form an upload since we dont know if the custom data injected dynamically
368 // is of type file etc $uploadNames = $this->get( 'uploadNames' );
369 $this->addButtons([
370 [
371 'type' => 'upload',
372 'name' => ts('Save'),
373 'js' => ['onclick' => "return verify( );"],
374 'isDefault' => TRUE,
375 ],
376 [
377 'type' => 'upload',
378 'name' => ts('Save and New'),
379 'js' => ['onclick' => "return verify( );"],
380 'subName' => 'new',
381 ],
382 [
383 'type' => 'cancel',
384 'name' => ts('Cancel'),
385 ],
386 ]);
387
388 $this->addFormRule(['CRM_Pledge_Form_Pledge', 'formRule'], $this);
389
390 if ($this->_action & CRM_Core_Action::VIEW) {
391 $this->freeze();
392 }
393 }
394
395 /**
396 * Global form rule.
397 *
398 * @param array $fields
399 * The input form values.
400 * @param array $files
401 * The uploaded files if any.
402 * @param $self
403 *
404 *
405 * @return bool|array
406 * true if no errors, else array of errors
407 */
408 public static function formRule($fields, $files, $self) {
409 $errors = [];
410
411 if ($fields['amount'] <= 0) {
412 $errors['amount'] = ts('Total Pledge Amount should be greater than zero.');
413 }
414 if ($fields['installments'] <= 0) {
415 $errors['installments'] = ts('Installments should be greater than zero.');
416 }
417
418 if ($fields['frequency_unit'] != 'week') {
419 if ($fields['frequency_day'] > 31 || $fields['frequency_day'] == 0) {
420 $errors['frequency_day'] = ts('Please enter a valid frequency day ie. 1 through 31.');
421 }
422 }
423 elseif ($fields['frequency_unit'] == 'week') {
424 if ($fields['frequency_day'] > 7 || $fields['frequency_day'] == 0) {
425 $errors['frequency_day'] = ts('Please enter a valid frequency day ie. 1 through 7.');
426 }
427 }
428 return $errors;
429 }
430
431 /**
432 * Process the form submission.
433 */
434 public function postProcess() {
435 if ($this->_action & CRM_Core_Action::DELETE) {
436 CRM_Pledge_BAO_Pledge::deletePledge($this->_id);
437 return;
438 }
439
440 // get the submitted form values.
441 $formValues = $this->controller->exportValues($this->_name);
442
443 // set the contact, when contact is selected
444 if (!empty($formValues['contact_id'])) {
445 $this->_contactID = $formValues['contact_id'];
446 }
447
448 $session = CRM_Core_Session::singleton();
449
450 $fields = [
451 'frequency_unit',
452 'frequency_interval',
453 'frequency_day',
454 'installments',
455 'financial_type_id',
456 'initial_reminder_day',
457 'max_reminders',
458 'additional_reminder_day',
459 'contribution_page_id',
460 'campaign_id',
461 ];
462 foreach ($fields as $f) {
463 $params[$f] = CRM_Utils_Array::value($f, $formValues);
464 }
465
466 // format amount
467 $params['amount'] = CRM_Utils_Rule::cleanMoney(CRM_Utils_Array::value('amount', $formValues));
468 $params['currency'] = CRM_Utils_Array::value('currency', $formValues);
469 $params['original_installment_amount'] = ($params['amount'] / $params['installments']);
470
471 $dates = ['create_date', 'start_date', 'acknowledge_date', 'cancel_date'];
472 foreach ($dates as $d) {
473 if ($this->_id && (!$this->_isPending) && !empty($this->_values[$d])) {
474 if ($d == 'start_date') {
475 $params['scheduled_date'] = CRM_Utils_Date::processDate($this->_values[$d]);
476 }
477 $params[$d] = CRM_Utils_Date::processDate($this->_values[$d]);
478 }
479 elseif (!empty($formValues[$d]) && !CRM_Utils_System::isNull($formValues[$d])) {
480 if ($d == 'start_date') {
481 $params['scheduled_date'] = CRM_Utils_Date::processDate($formValues[$d]);
482 }
483 $params[$d] = CRM_Utils_Date::processDate($formValues[$d]);
484 }
485 else {
486 $params[$d] = 'null';
487 }
488 }
489
490 if (!empty($formValues['is_acknowledge'])) {
491 $params['acknowledge_date'] = date('Y-m-d');
492 }
493
494 // assign id only in update mode
495 if ($this->_action & CRM_Core_Action::UPDATE) {
496 $params['id'] = $this->_id;
497 }
498
499 $params['contact_id'] = $this->_contactID;
500
501 // format custom data
502 if (!empty($formValues['hidden_custom'])) {
503 $params['hidden_custom'] = 1;
504
505 $customFields = CRM_Core_BAO_CustomField::getFields('Pledge');
506 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($formValues,
507 $this->_id,
508 'Pledge'
509 );
510 }
511
512 // handle pending pledge.
513 $params['is_pledge_pending'] = $this->_isPending;
514
515 // create pledge record.
516 $pledge = CRM_Pledge_BAO_Pledge::create($params);
517
518 $statusMsg = NULL;
519
520 if ($pledge->id) {
521 // set the status msg.
522 if ($this->_action & CRM_Core_Action::ADD) {
523 $statusMsg = ts('Pledge has been recorded and the payment schedule has been created.<br />');
524 }
525 elseif ($this->_action & CRM_Core_Action::UPDATE) {
526 $statusMsg = ts('Pledge has been updated.<br />');
527 }
528 }
529
530 // handle Acknowledgment.
531 if (!empty($formValues['is_acknowledge']) && $pledge->id) {
532
533 // calculate scheduled amount.
534 $params['scheduled_amount'] = round($params['amount'] / $params['installments']);
535 $params['total_pledge_amount'] = $params['amount'];
536 // get some required pledge values in params.
537 $params['id'] = $pledge->id;
538 $params['acknowledge_date'] = $pledge->acknowledge_date;
539 $params['is_test'] = $pledge->is_test;
540 $params['currency'] = $pledge->currency;
541 // retrieve 'from email id' for acknowledgement
542 $params['from_email_id'] = $formValues['from_email_address'];
543
544 $this->paymentId = NULL;
545 // send Acknowledgment mail.
546 CRM_Pledge_BAO_Pledge::sendAcknowledgment($this, $params);
547
548 $statusMsg .= ' ' . ts("An acknowledgment email has been sent to %1.<br />", [1 => $this->userEmail]);
549
550 // build the payment urls.
551 if ($this->paymentId) {
552 $urlParams = "reset=1&action=add&cid={$this->_contactID}&ppid={$this->paymentId}&context=pledge";
553 $contribURL = CRM_Utils_System::url('civicrm/contact/view/contribution', $urlParams);
554 $urlParams .= "&mode=live";
555 $creditURL = CRM_Utils_System::url('civicrm/contact/view/contribution', $urlParams);
556
557 // check if we can process credit card payment.
558 $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE,
559 "billing_mode IN ( 1, 3 )"
560 );
561 if (count($processors) > 0) {
562 $statusMsg .= ' ' . ts("If a payment is due now, you can record <a href='%1'>a check, EFT, or cash payment for this pledge</a> OR <a href='%2'>submit a credit card payment</a>.", [
563 1 => $contribURL,
564 2 => $creditURL,
565 ]);
566 }
567 else {
568 $statusMsg .= ' ' . ts("If a payment is due now, you can record <a href='%1'>a check, EFT, or cash payment for this pledge</a>.", [1 => $contribURL]);
569 }
570 }
571 }
572 CRM_Core_Session::setStatus($statusMsg, ts('Payment Due'), 'info');
573
574 $buttonName = $this->controller->getButtonName();
575 if ($this->_context == 'standalone') {
576 if ($buttonName == $this->getButtonName('upload', 'new')) {
577 $session->replaceUserContext(CRM_Utils_System::url('civicrm/pledge/add',
578 'reset=1&action=add&context=standalone'
579 ));
580 }
581 else {
582 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view',
583 "reset=1&cid={$this->_contactID}&selectedChild=pledge"
584 ));
585 }
586 }
587 elseif ($buttonName == $this->getButtonName('upload', 'new')) {
588 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view/pledge',
589 "reset=1&action=add&context=pledge&cid={$this->_contactID}"
590 ));
591 }
592 }
593
594 }