Merge pull request #18049 from christianwach/lab-core-1891
[civicrm-core.git] / CRM / Contribute / Form / Task / Invoice.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * This class provides the functionality to email a group of
20 * contacts.
21 */
22 class CRM_Contribute_Form_Task_Invoice extends CRM_Contribute_Form_Task {
23 /**
24 * Are we operating in "single mode", i.e. updating the task of only
25 * one specific contribution?
26 *
27 * @var bool
28 */
29 public $_single = FALSE;
30
31 /**
32 * Gives all the statues for conribution.
33 * @var int
34 */
35 public $_contributionStatusId;
36
37 /**
38 * Gives the HTML template of PDF Invoice.
39 * @var string
40 */
41 public $_messageInvoice;
42
43 /**
44 * This variable is used to assign parameters for HTML template of PDF Invoice.
45 * @var string
46 */
47 public $_invoiceTemplate;
48
49 /**
50 * Selected output.
51 * @var string
52 */
53 public $_selectedOutput;
54
55 /**
56 * Build all the data structures needed to build the form.
57 */
58 public function preProcess() {
59 $id = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE);
60 if ($id) {
61 $this->_contributionIds = [$id];
62 $this->_componentClause = " civicrm_contribution.id IN ( $id ) ";
63 $this->_single = TRUE;
64 $this->assign('totalSelectedContributions', 1);
65
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"
70 );
71
72 CRM_Core_Session::singleton()->pushUserContext($url);
73 }
74 else {
75 parent::preProcess();
76 }
77
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'];
81 $statusId = [];
82 foreach ($this->_contributionStatusId as $key => $value) {
83 if (in_array($value, $status)) {
84 $statusId[] = $key;
85 }
86 }
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);
90 if ($count != 0) {
91 CRM_Core_Error::statusBounce(ts('Please select only contributions with Completed, Pending, Refunded, or Partially Paid status.'));
92 }
93
94 // we have all the contribution ids, so now we get the contact ids
95 parent::setContactIDs();
96 $this->assign('single', $this->_single);
97
98 $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this);
99 $urlParams = 'force=1';
100 if (CRM_Utils_Rule::qfKey($qfKey)) {
101 $urlParams .= "&qfKey=$qfKey";
102 }
103
104 $url = CRM_Utils_System::url('civicrm/contribute/search', $urlParams);
105 $breadCrumb = [
106 [
107 'url' => $url,
108 'title' => ts('Search Results'),
109 ],
110 ];
111
112 CRM_Utils_System::appendBreadCrumb($breadCrumb);
113
114 $this->_selectedOutput = CRM_Utils_Request::retrieve('select', 'String', $this);
115 $this->assign('selectedOutput', $this->_selectedOutput);
116
117 CRM_Contact_Form_Task_EmailCommon::preProcessFromAddress($this);
118 if ($this->_selectedOutput == 'email') {
119 CRM_Utils_System::setTitle(ts('Email Invoice'));
120 }
121 else {
122 CRM_Utils_System::setTitle(ts('Print Contribution Invoice'));
123 }
124 }
125
126 /**
127 * Build the form object.
128 */
129 public function buildQuickForm() {
130 $this->preventAjaxSubmit();
131 if (CRM_Core_Permission::check('administer CiviCRM')) {
132 $this->assign('isAdmin', 1);
133 }
134
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']);
141 }
142 else {
143 $this->addRule('from_email_address', ts('From Email Address is required'), 'required');
144 }
145
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.)'), [
147 'rows' => 2,
148 'cols' => 40,
149 ]);
150
151 $this->addButtons([
152 [
153 'type' => 'upload',
154 'name' => $this->_selectedOutput == 'email' ? ts('Send Email') : ts('Process Invoice(s)'),
155 'isDefault' => TRUE,
156 ],
157 [
158 'type' => 'cancel',
159 'name' => ts('Cancel'),
160 ],
161 ]);
162 }
163
164 /**
165 * Global validation rules for the form.
166 *
167 * @param array $values
168 *
169 * @return array
170 * list of errors to be posted back to the form
171 */
172 public static function formRule($values) {
173 $errors = [];
174
175 if ($values['output'] == 'email_invoice' && empty($values['from_email_address'])) {
176 $errors['from_email_address'] = ts("From Email Address is required");
177 }
178
179 return $errors;
180 }
181
182 /**
183 * Process the form after the input has been submitted and validated.
184 */
185 public function postProcess() {
186 $params = $this->controller->exportValues($this->_name);
187 self::printPDF($this->_contributionIds, $params, $this->_contactIds);
188 }
189
190 /**
191 * Process the PDf and email with activity and attachment on click of Print Invoices.
192 *
193 * @param array $contribIDs
194 * Contribution Id.
195 * @param array $params
196 * Associated array of submitted values.
197 * @param array $contactIds
198 * Contact Id.
199 */
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);
205
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);
211
212 foreach ($invoiceElements['details'] as $contribID => $detail) {
213 $input = $ids = $objects = [];
214 if (in_array($detail['contact'], $invoiceElements['excludeContactIds'])) {
215 continue;
216 }
217
218 $input['component'] = $detail['component'];
219
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;
227
228 if (!$invoiceElements['baseIPN']->validateData($input, $ids, $objects, FALSE)) {
229 CRM_Core_Error::statusBounce('Supplied data was not able to be validated');
230 }
231
232 $contribution = &$objects['contribution'];
233
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;
239
240 $objects['contribution']->receive_date = CRM_Utils_Date::isoToMysql($objects['contribution']->receive_date);
241
242 // Fetch the billing address. getValues should prioritize the billing
243 // address, otherwise will return the primary address.
244 $billingAddress = [];
245
246 $addressDetails = CRM_Core_BAO_Address::getValues([
247 'contact_id' => $contribution->contact_id,
248 'is_billing' => 1,
249 ]);
250
251 if (!empty($addressDetails)) {
252 $billingAddress = array_shift($addressDetails);
253 }
254
255 if ($contribution->contribution_status_id == $refundedStatusId || $contribution->contribution_status_id == $cancelledStatusId) {
256 $creditNoteId = $contribution->creditnote_id;
257 }
258 if (!$contribution->invoice_number) {
259 $contribution->invoice_number = CRM_Contribute_BAO_Contribution::getInvoiceNumber($contribution->id);
260 }
261
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));
268
269 $amountPaid = CRM_Core_BAO_FinancialTrxn::getTotalPayments($contribID, TRUE);
270 $amountDue = ($input['amount'] - $amountPaid);
271
272 // retrieving the subtotal and sum of same tax_rate
273 $dataArray = [];
274 $subTotal = 0;
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);
279 }
280 else {
281 $dataArray[(string) $taxRate['tax_rate']] = $taxRate['tax_amount'] ?? NULL;
282 }
283 $subTotal += CRM_Utils_Array::value('subTotal', $taxRate);
284 }
285
286 // to email the invoice
287 $mailDetails = [];
288 $values = [];
289 if ($contribution->_component == 'event') {
290 $daoName = 'CRM_Event_DAO_Event';
291 $pageId = $contribution->_relatedObjects['event']->id;
292 $mailElements = [
293 'title',
294 'confirm_from_name',
295 'confirm_from_email',
296 ];
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;
301
302 $title = $mailDetails[$contribution->_relatedObjects['event']->id]['title'] ?? NULL;
303 }
304 elseif ($contribution->_component == 'contribute') {
305 $daoName = 'CRM_Contribute_DAO_ContributionPage';
306 $pageId = $contribution->contribution_page_id;
307 $mailElements = [
308 'title',
309 'receipt_from_name',
310 'receipt_from_email',
311 'cc_receipt',
312 'bcc_receipt',
313 ];
314 CRM_Core_DAO::commonRetrieveAll($daoName, 'id', $pageId, $mailDetails, $mailElements);
315
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));
321
322 $title = CRM_Utils_Array::value('title', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails));
323 }
324 $source = $contribution->source;
325
326 $config = CRM_Core_Config::singleton();
327 if (!isset($params['forPage'])) {
328 $config->doNotAttachPDFReceipt = 1;
329 }
330
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']);
337 }
338 else {
339 $stateProvinceAbbreviationDomain = '';
340 }
341 if (isset($locationDefaults['address'][1]['country_id'])) {
342 $countryDomain = CRM_Core_PseudoConstant::country($locationDefaults['address'][1]['country_id']);
343 }
344 else {
345 $countryDomain = '';
346 }
347
348 $invoiceNotes = Civi::settings()->get('invoice_notes') ?? NULL;
349
350 // parameters to be assign for template
351 $tplParams = [
352 'title' => $title,
353 'component' => $input['component'],
354 'id' => $contribution->id,
355 'source' => $source,
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'])),
399 ];
400
401 if (isset($creditNoteId)) {
402 $tplParams['creditnote_id'] = $creditNoteId;
403 }
404
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,
412 ];
413
414 // from email address
415 $fromEmailAddress = $params['from_email_address'] ?? NULL;
416
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'])) {
421 return $html;
422 }
423 else {
424 $mail = [
425 'subject' => $subject,
426 'body' => $message,
427 'html' => $html,
428 ];
429 if ($mail['html']) {
430 $messageInvoice[] = $mail['html'];
431 }
432 else {
433 $messageInvoice[] = nl2br($mail['body']);
434 }
435 }
436 }
437 elseif ($contribution->_component == 'contribute') {
438 $email = CRM_Contact_BAO_Contact::getPrimaryEmail($contribution->contact_id);
439
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;
445
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);
450 }
451 elseif ($contribution->_component == 'event') {
452 $email = CRM_Contact_BAO_Contact::getPrimaryEmail($contribution->contact_id);
453
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;
459
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);
464 }
465 $invoiceTemplate->clearTemplateVars();
466 }
467
468 if ($invoiceElements['createPdf']) {
469 if (isset($params['forPage'])) {
470 return $html;
471 }
472 else {
473 CRM_Utils_PDF_Utils::html2pdf($messageInvoice, $pdfFileName, FALSE, [
474 'margin_top' => 10,
475 'margin_left' => 65,
476 'metric' => 'px',
477 ]);
478 // functions call for adding activity with attachment
479 $fileName = self::putFile($html, $pdfFileName, [
480 'margin_top' => 10,
481 'margin_left' => 65,
482 'metric' => 'px',
483 ]);
484 self::addActivities($subject, $contactIds, $fileName, $params);
485
486 CRM_Utils_System::civiExit();
487 }
488 }
489 else {
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');
493 $msgType = 'error';
494 }
495 else {
496 $status = ts('Your mail has been sent.');
497 $msgTitle = ts('Sent');
498 $msgType = 'success';
499 }
500 CRM_Core_Session::setStatus($status, $msgTitle, $msgType);
501 }
502 }
503
504 /**
505 * Add activity for Email Invoice and the PDF Invoice.
506 *
507 * @param string $subject
508 * Activity subject.
509 * @param array $contactIds
510 * Contact Id.
511 * @param string $fileName
512 * Gives the location with name of the file.
513 * @param array $params
514 * For invoices.
515 * @param int $contributionId
516 * Contribution Id.
517 *
518 */
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;
524
525 if (!empty($params['output']) && $params['output'] == 'pdf_invoice') {
526 $activityType = 'Downloaded Invoice';
527 }
528 else {
529 $activityType = 'Emailed Invoice';
530 }
531
532 $activityParams = [
533 'subject' => $subject,
534 'source_contact_id' => $userID,
535 'target_contact_id' => $contactIds,
536 'activity_type_id' => $activityType,
537 'activity_date_time' => date('YmdHis'),
538 'attachFile_1' => [
539 'uri' => $fileName,
540 'type' => 'application/pdf',
541 'location' => $fileName,
542 'upload_date' => date('YmdHis'),
543 ],
544 ];
545 if ($contributionId) {
546 $activityParams['source_record_id'] = $contributionId;
547 }
548 civicrm_api3('Activity', 'create', $activityParams);
549 }
550
551 /**
552 * Create the Invoice file in upload folder for attachment.
553 *
554 * @param string $html
555 * Content for pdf in html format.
556 *
557 * @param string $name
558 * @param array $format
559 *
560 * @return string
561 * Name of file which is in pdf format
562 */
563 public static function putFile($html, $name = 'Invoice.pdf', $format = NULL) {
564 return CRM_Utils_Mail::appendPDF($name, $html, $format)['fullPath'] ?? '';
565 }
566
567 /**
568 * Callback to perform action on Print Invoice button.
569 */
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);
576 }
577
578 }