3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 * This class provides the functionality to email a group of
22 class CRM_Contribute_Form_Task_Invoice
extends CRM_Contribute_Form_Task
{
24 * Are we operating in "single mode", i.e. updating the task of only
25 * one specific contribution?
29 public $_single = FALSE;
32 * Gives all the statues for conribution.
35 public $_contributionStatusId;
38 * Gives the HTML template of PDF Invoice.
41 public $_messageInvoice;
44 * This variable is used to assign parameters for HTML template of PDF Invoice.
47 public $_invoiceTemplate;
53 public $_selectedOutput;
56 * Build all the data structures needed to build the form.
58 public function preProcess() {
59 $id = CRM_Utils_Request
::retrieve('id', 'Positive', $this, FALSE);
61 $this->_contributionIds
= [$id];
62 $this->_componentClause
= " civicrm_contribution.id IN ( $id ) ";
63 $this->_single
= TRUE;
64 $this->assign('totalSelectedContributions', 1);
66 // set the redirection after actions
67 $contactId = CRM_Utils_Request
::retrieve('cid', 'Positive', $this, FALSE);
68 $url = CRM_Utils_System
::url('civicrm/contact/view/contribution',
69 "action=view&reset=1&id={$id}&cid={$contactId}&context=contribution&selectedChild=contribute"
72 CRM_Core_Session
::singleton()->pushUserContext($url);
78 // check that all the contribution ids have status Completed, Pending, Refunded, or Partially Paid.
79 $this->_contributionStatusId
= CRM_Contribute_PseudoConstant
::contributionStatus(NULL, 'name');
80 $status = ['Completed', 'Pending', 'Refunded', 'Partially paid'];
82 foreach ($this->_contributionStatusId
as $key => $value) {
83 if (in_array($value, $status)) {
87 $Id = implode(",", $statusId);
88 $query = "SELECT count(*) FROM civicrm_contribution WHERE contribution_status_id NOT IN ($Id) AND {$this->_componentClause}";
89 $count = CRM_Core_DAO
::singleValueQuery($query);
91 CRM_Core_Error
::statusBounce(ts('Please select only contributions with Completed, Pending, Refunded, or Partially Paid status.'));
94 // we have all the contribution ids, so now we get the contact ids
95 parent
::setContactIDs();
96 $this->assign('single', $this->_single
);
98 $qfKey = CRM_Utils_Request
::retrieve('qfKey', 'String', $this);
99 $urlParams = 'force=1';
100 if (CRM_Utils_Rule
::qfKey($qfKey)) {
101 $urlParams .= "&qfKey=$qfKey";
104 $url = CRM_Utils_System
::url('civicrm/contribute/search', $urlParams);
108 'title' => ts('Search Results'),
112 CRM_Utils_System
::appendBreadCrumb($breadCrumb);
114 $this->_selectedOutput
= CRM_Utils_Request
::retrieve('select', 'String', $this);
115 $this->assign('selectedOutput', $this->_selectedOutput
);
117 CRM_Contact_Form_Task_EmailCommon
::preProcessFromAddress($this);
118 if ($this->_selectedOutput
== 'email') {
119 CRM_Utils_System
::setTitle(ts('Email Invoice'));
122 CRM_Utils_System
::setTitle(ts('Print Contribution Invoice'));
127 * Build the form object.
129 public function buildQuickForm() {
130 $this->preventAjaxSubmit();
131 if (CRM_Core_Permission
::check('administer CiviCRM')) {
132 $this->assign('isAdmin', 1);
135 $this->add('select', 'from_email_address', ts('From'), $this->_fromEmails
, TRUE);
136 if ($this->_selectedOutput
!= 'email') {
137 $this->addElement('radio', 'output', NULL, ts('Email Invoice'), 'email_invoice');
138 $this->addElement('radio', 'output', NULL, ts('PDF Invoice'), 'pdf_invoice');
139 $this->addRule('output', ts('Selection required'), 'required');
140 $this->addFormRule(['CRM_Contribute_Form_Task_Invoice', 'formRule']);
143 $this->addRule('from_email_address', ts('From Email Address is required'), 'required');
146 $this->add('wysiwyg', 'email_comment', ts('If you would like to add personal message to email please add it here. (If sending to more then one receipient the same message will be sent to each contact.)'), [
154 'name' => $this->_selectedOutput
== 'email' ?
ts('Send Email') : ts('Process Invoice(s)'),
159 'name' => ts('Cancel'),
165 * Global validation rules for the form.
167 * @param array $values
170 * list of errors to be posted back to the form
172 public static function formRule($values) {
175 if ($values['output'] == 'email_invoice' && empty($values['from_email_address'])) {
176 $errors['from_email_address'] = ts("From Email Address is required");
183 * Process the form after the input has been submitted and validated.
185 public function postProcess() {
186 $params = $this->controller
->exportValues($this->_name
);
187 self
::printPDF($this->_contributionIds
, $params, $this->_contactIds
);
191 * Process the PDf and email with activity and attachment on click of Print Invoices.
193 * @param array $contribIDs
195 * @param array $params
196 * Associated array of submitted values.
197 * @param array $contactIds
200 public static function printPDF($contribIDs, &$params, $contactIds) {
201 // get all the details needed to generate a invoice
202 $messageInvoice = [];
203 $invoiceTemplate = CRM_Core_Smarty
::singleton();
204 $invoiceElements = CRM_Contribute_Form_Task_PDF
::getElements($contribIDs, $params, $contactIds);
206 // gives the status id when contribution status is 'Refunded'
207 $contributionStatusID = CRM_Contribute_PseudoConstant
::contributionStatus(NULL, 'name');
208 $refundedStatusId = CRM_Utils_Array
::key('Refunded', $contributionStatusID);
209 $cancelledStatusId = CRM_Utils_Array
::key('Cancelled', $contributionStatusID);
210 $pendingStatusId = CRM_Utils_Array
::key('Pending', $contributionStatusID);
212 foreach ($invoiceElements['details'] as $contribID => $detail) {
213 $input = $ids = $objects = [];
214 if (in_array($detail['contact'], $invoiceElements['excludeContactIds'])) {
218 $input['component'] = $detail['component'];
220 $ids['contact'] = $detail['contact'];
221 $ids['contribution'] = $contribID;
222 $ids['contributionRecur'] = NULL;
223 $ids['contributionPage'] = NULL;
224 $ids['membership'] = $detail['membership'] ??
NULL;
225 $ids['participant'] = $detail['participant'] ??
NULL;
226 $ids['event'] = $detail['event'] ??
NULL;
228 if (!$invoiceElements['baseIPN']->validateData($input, $ids, $objects, FALSE)) {
229 CRM_Core_Error
::statusBounce('Supplied data was not able to be validated');
232 $contribution = &$objects['contribution'];
234 $input['amount'] = $contribution->total_amount
;
235 $input['invoice_id'] = $contribution->invoice_id
;
236 $input['receive_date'] = $contribution->receive_date
;
237 $input['contribution_status_id'] = $contribution->contribution_status_id
;
238 $input['organization_name'] = $contribution->_relatedObjects
['contact']->organization_name
;
240 $objects['contribution']->receive_date
= CRM_Utils_Date
::isoToMysql($objects['contribution']->receive_date
);
242 // Fetch the billing address. getValues should prioritize the billing
243 // address, otherwise will return the primary address.
244 $billingAddress = [];
246 $addressDetails = CRM_Core_BAO_Address
::getValues([
247 'contact_id' => $contribution->contact_id
,
251 if (!empty($addressDetails)) {
252 $billingAddress = array_shift($addressDetails);
255 if ($contribution->contribution_status_id
== $refundedStatusId ||
$contribution->contribution_status_id
== $cancelledStatusId) {
256 $creditNoteId = $contribution->creditnote_id
;
258 if (!$contribution->invoice_number
) {
259 $contribution->invoice_number
= CRM_Contribute_BAO_Contribution
::getInvoiceNumber($contribution->id
);
262 //to obtain due date for PDF invoice
263 $contributionReceiveDate = date('F j,Y', strtotime(date($input['receive_date'])));
264 $invoiceDate = date("F j, Y");
265 $dueDateSetting = Civi
::settings()->get('invoice_due_date');
266 $dueDatePeriodSetting = Civi
::settings()->get('invoice_due_date_period');
267 $dueDate = date('F j, Y', strtotime($contributionReceiveDate . "+" . $dueDateSetting . "" . $dueDatePeriodSetting));
269 $amountPaid = CRM_Core_BAO_FinancialTrxn
::getTotalPayments($contribID, TRUE);
270 $amountDue = ($input['amount'] - $amountPaid);
272 // retrieving the subtotal and sum of same tax_rate
275 $lineItem = CRM_Price_BAO_LineItem
::getLineItemsByContributionID($contribID);
276 foreach ($lineItem as $taxRate) {
277 if (isset($dataArray[(string) $taxRate['tax_rate']])) {
278 $dataArray[(string) $taxRate['tax_rate']] = $dataArray[(string) $taxRate['tax_rate']] + CRM_Utils_Array
::value('tax_amount', $taxRate);
281 $dataArray[(string) $taxRate['tax_rate']] = $taxRate['tax_amount'] ??
NULL;
283 $subTotal +
= CRM_Utils_Array
::value('subTotal', $taxRate);
286 // to email the invoice
289 if ($contribution->_component
== 'event') {
290 $daoName = 'CRM_Event_DAO_Event';
291 $pageId = $contribution->_relatedObjects
['event']->id
;
295 'confirm_from_email',
297 CRM_Core_DAO
::commonRetrieveAll($daoName, 'id', $pageId, $mailDetails, $mailElements);
298 $values['title'] = $mailDetails[$contribution->_relatedObjects
['event']->id
]['title'] ??
NULL;
299 $values['confirm_from_name'] = $mailDetails[$contribution->_relatedObjects
['event']->id
]['confirm_from_name'] ??
NULL;
300 $values['confirm_from_email'] = $mailDetails[$contribution->_relatedObjects
['event']->id
]['confirm_from_email'] ??
NULL;
302 $title = $mailDetails[$contribution->_relatedObjects
['event']->id
]['title'] ??
NULL;
304 elseif ($contribution->_component
== 'contribute') {
305 $daoName = 'CRM_Contribute_DAO_ContributionPage';
306 $pageId = $contribution->contribution_page_id
;
310 'receipt_from_email',
314 CRM_Core_DAO
::commonRetrieveAll($daoName, 'id', $pageId, $mailDetails, $mailElements);
316 $values['title'] = CRM_Utils_Array
::value('title', CRM_Utils_Array
::value($contribution->contribution_page_id
, $mailDetails));
317 $values['receipt_from_name'] = CRM_Utils_Array
::value('receipt_from_name', CRM_Utils_Array
::value($contribution->contribution_page_id
, $mailDetails));
318 $values['receipt_from_email'] = CRM_Utils_Array
::value('receipt_from_email', CRM_Utils_Array
::value($contribution->contribution_page_id
, $mailDetails));
319 $values['cc_receipt'] = CRM_Utils_Array
::value('cc_receipt', CRM_Utils_Array
::value($contribution->contribution_page_id
, $mailDetails));
320 $values['bcc_receipt'] = CRM_Utils_Array
::value('bcc_receipt', CRM_Utils_Array
::value($contribution->contribution_page_id
, $mailDetails));
322 $title = CRM_Utils_Array
::value('title', CRM_Utils_Array
::value($contribution->contribution_page_id
, $mailDetails));
324 $source = $contribution->source
;
326 $config = CRM_Core_Config
::singleton();
327 if (!isset($params['forPage'])) {
328 $config->doNotAttachPDFReceipt
= 1;
331 // get organization address
332 $domain = CRM_Core_BAO_Domain
::getDomain();
333 $locParams = ['contact_id' => $domain->contact_id
];
334 $locationDefaults = CRM_Core_BAO_Location
::getValues($locParams);
335 if (isset($locationDefaults['address'][1]['state_province_id'])) {
336 $stateProvinceAbbreviationDomain = CRM_Core_PseudoConstant
::stateProvinceAbbreviation($locationDefaults['address'][1]['state_province_id']);
339 $stateProvinceAbbreviationDomain = '';
341 if (isset($locationDefaults['address'][1]['country_id'])) {
342 $countryDomain = CRM_Core_PseudoConstant
::country($locationDefaults['address'][1]['country_id']);
348 $invoiceNotes = Civi
::settings()->get('invoice_notes') ??
NULL;
350 // parameters to be assign for template
353 'component' => $input['component'],
354 'id' => $contribution->id
,
356 'invoice_number' => $contribution->invoice_number
,
357 'invoice_id' => $contribution->invoice_id
,
358 'resourceBase' => $config->userFrameworkResourceURL
,
359 'defaultCurrency' => $config->defaultCurrency
,
360 'amount' => $contribution->total_amount
,
361 'amountDue' => $amountDue,
362 'amountPaid' => $amountPaid,
363 'invoice_date' => $invoiceDate,
364 'dueDate' => $dueDate,
365 'notes' => $invoiceNotes,
366 'display_name' => $contribution->_relatedObjects
['contact']->display_name
,
367 'lineItem' => $lineItem,
368 'dataArray' => $dataArray,
369 'refundedStatusId' => $refundedStatusId,
370 'pendingStatusId' => $pendingStatusId,
371 'cancelledStatusId' => $cancelledStatusId,
372 'contribution_status_id' => $contribution->contribution_status_id
,
373 'contributionStatusName' => CRM_Core_PseudoConstant
::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contribution->contribution_status_id
),
374 'subTotal' => $subTotal,
375 'street_address' => $billingAddress['street_address'] ??
NULL,
376 'supplemental_address_1' => $billingAddress['supplemental_address_1'] ??
NULL,
377 'supplemental_address_2' => $billingAddress['supplemental_address_2'] ??
NULL,
378 'supplemental_address_3' => $billingAddress['supplemental_address_3'] ??
NULL,
379 'city' => $billingAddress['city'] ??
NULL,
380 'postal_code' => $billingAddress['postal_code'] ??
NULL,
381 'state_province' => $billingAddress['state_province'] ??
NULL,
382 'state_province_abbreviation' => $billingAddress['state_province_abbreviation'] ??
NULL,
383 // Kept for backwards compatibility
384 'stateProvinceAbbreviation' => $billingAddress['state_province_abbreviation'] ??
NULL,
385 'country' => $billingAddress['country'] ??
NULL,
386 'is_pay_later' => $contribution->is_pay_later
,
387 'organization_name' => $contribution->_relatedObjects
['contact']->organization_name
,
388 'domain_organization' => $domain->name
,
389 'domain_street_address' => CRM_Utils_Array
::value('street_address', CRM_Utils_Array
::value('1', $locationDefaults['address'])),
390 'domain_supplemental_address_1' => CRM_Utils_Array
::value('supplemental_address_1', CRM_Utils_Array
::value('1', $locationDefaults['address'])),
391 'domain_supplemental_address_2' => CRM_Utils_Array
::value('supplemental_address_2', CRM_Utils_Array
::value('1', $locationDefaults['address'])),
392 'domain_supplemental_address_3' => CRM_Utils_Array
::value('supplemental_address_3', CRM_Utils_Array
::value('1', $locationDefaults['address'])),
393 'domain_city' => CRM_Utils_Array
::value('city', CRM_Utils_Array
::value('1', $locationDefaults['address'])),
394 'domain_postal_code' => CRM_Utils_Array
::value('postal_code', CRM_Utils_Array
::value('1', $locationDefaults['address'])),
395 'domain_state' => $stateProvinceAbbreviationDomain,
396 'domain_country' => $countryDomain,
397 'domain_email' => CRM_Utils_Array
::value('email', CRM_Utils_Array
::value('1', $locationDefaults['email'])),
398 'domain_phone' => CRM_Utils_Array
::value('phone', CRM_Utils_Array
::value('1', $locationDefaults['phone'])),
401 if (isset($creditNoteId)) {
402 $tplParams['creditnote_id'] = $creditNoteId;
405 $pdfFileName = $contribution->invoice_number
. ".pdf";
406 $sendTemplateParams = [
407 'groupName' => 'msg_tpl_workflow_contribution',
408 'valueName' => 'contribution_invoice_receipt',
409 'contactId' => $contribution->contact_id
,
410 'tplParams' => $tplParams,
411 'PDFFilename' => $pdfFileName,
414 // from email address
415 $fromEmailAddress = $params['from_email_address'] ??
NULL;
417 // condition to check for download PDF Invoice or email Invoice
418 if ($invoiceElements['createPdf']) {
419 list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate
::sendTemplate($sendTemplateParams);
420 if (isset($params['forPage'])) {
425 'subject' => $subject,
430 $messageInvoice[] = $mail['html'];
433 $messageInvoice[] = nl2br($mail['body']);
437 elseif ($contribution->_component
== 'contribute') {
438 $email = CRM_Contact_BAO_Contact
::getPrimaryEmail($contribution->contact_id
);
440 $sendTemplateParams['tplParams'] = array_merge($tplParams, ['email_comment' => $invoiceElements['params']['email_comment']]);
441 $sendTemplateParams['from'] = $fromEmailAddress;
442 $sendTemplateParams['toEmail'] = $email;
443 $sendTemplateParams['cc'] = $values['cc_receipt'] ??
NULL;
444 $sendTemplateParams['bcc'] = $values['bcc_receipt'] ??
NULL;
446 list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate
::sendTemplate($sendTemplateParams);
447 // functions call for adding activity with attachment
448 $fileName = self
::putFile($html, $pdfFileName);
449 self
::addActivities($subject, $contribution->contact_id
, $fileName, $params, $contribution->id
);
451 elseif ($contribution->_component
== 'event') {
452 $email = CRM_Contact_BAO_Contact
::getPrimaryEmail($contribution->contact_id
);
454 $sendTemplateParams['tplParams'] = array_merge($tplParams, ['email_comment' => $invoiceElements['params']['email_comment']]);
455 $sendTemplateParams['from'] = $fromEmailAddress;
456 $sendTemplateParams['toEmail'] = $email;
457 $sendTemplateParams['cc'] = $values['cc_confirm'] ??
NULL;
458 $sendTemplateParams['bcc'] = $values['bcc_confirm'] ??
NULL;
460 list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate
::sendTemplate($sendTemplateParams);
461 // functions call for adding activity with attachment
462 $fileName = self
::putFile($html, $pdfFileName);
463 self
::addActivities($subject, $contribution->contact_id
, $fileName, $params, $contribution->id
);
465 $invoiceTemplate->clearTemplateVars();
468 if ($invoiceElements['createPdf']) {
469 if (isset($params['forPage'])) {
473 CRM_Utils_PDF_Utils
::html2pdf($messageInvoice, $pdfFileName, FALSE, [
478 // functions call for adding activity with attachment
479 $fileName = self
::putFile($html, $pdfFileName, [
484 self
::addActivities($subject, $contactIds, $fileName, $params);
486 CRM_Utils_System
::civiExit();
490 if ($invoiceElements['suppressedEmails']) {
491 $status = ts('Email was NOT sent to %1 contacts (no email address on file, or communication preferences specify DO NOT EMAIL, or contact is deceased).', [1 => $invoiceElements['suppressedEmails']]);
492 $msgTitle = ts('Email Error');
496 $status = ts('Your mail has been sent.');
497 $msgTitle = ts('Sent');
498 $msgType = 'success';
500 CRM_Core_Session
::setStatus($status, $msgTitle, $msgType);
505 * Add activity for Email Invoice and the PDF Invoice.
507 * @param string $subject
509 * @param array $contactIds
511 * @param string $fileName
512 * Gives the location with name of the file.
513 * @param array $params
515 * @param int $contributionId
519 public static function addActivities($subject, $contactIds, $fileName, $params, $contributionId = NULL) {
520 $session = CRM_Core_Session
::singleton();
521 $userID = $session->get('userID');
522 $config = CRM_Core_Config
::singleton();
523 $config->doNotAttachPDFReceipt
= 1;
525 if (!empty($params['output']) && $params['output'] == 'pdf_invoice') {
526 $activityType = 'Downloaded Invoice';
529 $activityType = 'Emailed Invoice';
533 'subject' => $subject,
534 'source_contact_id' => $userID,
535 'target_contact_id' => $contactIds,
536 'activity_type_id' => $activityType,
537 'activity_date_time' => date('YmdHis'),
540 'type' => 'application/pdf',
541 'location' => $fileName,
542 'upload_date' => date('YmdHis'),
545 if ($contributionId) {
546 $activityParams['source_record_id'] = $contributionId;
548 civicrm_api3('Activity', 'create', $activityParams);
552 * Create the Invoice file in upload folder for attachment.
554 * @param string $html
555 * Content for pdf in html format.
557 * @param string $name
558 * @param array $format
561 * Name of file which is in pdf format
563 public static function putFile($html, $name = 'Invoice.pdf', $format = NULL) {
564 return CRM_Utils_Mail
::appendPDF($name, $html, $format)['fullPath'] ??
'';
568 * Callback to perform action on Print Invoice button.
570 public static function getPrintPDF() {
571 $contributionId = CRM_Utils_Request
::retrieve('id', 'Positive', CRM_Core_DAO
::$_nullObject, FALSE);
572 $contributionIDs = [$contributionId];
573 $contactId = CRM_Utils_Request
::retrieve('cid', 'Positive', CRM_Core_DAO
::$_nullObject, FALSE);
574 $params = ['output' => 'pdf_invoice'];
575 CRM_Contribute_Form_Task_Invoice
::printPDF($contributionIDs, $params, $contactId);