Merge pull request #23102 from seamuslee001/dompdf_update
[civicrm-core.git] / CRM / Contribute / Form / ContributionView.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 use Civi\Api4\Contribution;
13
14 /**
15 *
16 * @package CRM
17 * @copyright CiviCRM LLC https://civicrm.org/licensing
18 */
19
20 /**
21 * This class generates form components for Payment-Instrument.
22 */
23 class CRM_Contribute_Form_ContributionView extends CRM_Core_Form {
24
25 /**
26 * Set variables up before form is built.
27 *
28 * @throws \CRM_Core_Exception
29 * @throws \API_Exception
30 */
31 public function preProcess() {
32 $id = $this->getID();
33
34 // Check permission for action.
35 if (!CRM_Core_Permission::checkActionPermission('CiviContribute', $this->_action)) {
36 CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.'));
37 }
38 $params = ['id' => $id];
39 $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this);
40 $this->assign('context', $context);
41
42 // Note than this get could be restricted by ACLs in an extension
43 $contribution = Contribution::get(TRUE)->addWhere('id', '=', $id)->addSelect('*')->execute()->first();
44 if (empty($contribution)) {
45 CRM_Core_Error::statusBounce(ts('Access to contribution not permitted'));
46 }
47 // We just cast here because it was traditionally an array called values - would be better
48 // just to use 'contribution'.
49 $values = (array) $contribution;
50 $contributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $values['contribution_status_id']);
51
52 if (!isset($this->get_template_vars()['hookDiscount'])) {
53 $this->assign('hookDiscount', ['message' => '']);
54 }
55 $this->addExpectedSmartyVariables([
56 'pricesetFieldsCount',
57 'pcp_id',
58 'getTaxDetails',
59 // currencySymbol maybe doesn't make sense but is probably old?
60 'currencySymbol',
61 ]);
62
63 // @todo - it might have been better to create a new form that extends this
64 // for template contributions rather than overloading this form.
65 $force_create_template = CRM_Utils_Request::retrieve('force_create_template', 'Boolean', $this, FALSE, FALSE);
66 if ($force_create_template && !empty($values['contribution_recur_id']) && empty($values['is_template'])) {
67 // Create a template contribution.
68 $templateContributionId = CRM_Contribute_BAO_ContributionRecur::ensureTemplateContributionExists($values['contribution_recur_id']);
69 if (!empty($templateContributionId)) {
70 $id = $templateContributionId;
71 $params = ['id' => $id];
72 $values = CRM_Contribute_BAO_Contribution::getValuesWithMappings($params);
73 }
74 }
75 $this->assign('is_template', $values['is_template']);
76
77 CRM_Contribute_BAO_Contribution::resolveDefaults($values);
78
79 $values['contribution_page_title'] = '';
80 if (!empty($values['contribution_page_id'])) {
81 $contribPages = CRM_Contribute_PseudoConstant::contributionPage(NULL, TRUE);
82 $values['contribution_page_title'] = CRM_Utils_Array::value(CRM_Utils_Array::value('contribution_page_id', $values), $contribPages);
83 }
84
85 // get received into i.e to_financial_account_id from last trxn
86 $financialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($this->getID(), 'DESC');
87 $values['to_financial_account'] = '';
88 $values['payment_processor_name'] = '';
89 if (!empty($financialTrxnId['financialTrxnId'])) {
90 $values['to_financial_account_id'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialTrxn', $financialTrxnId['financialTrxnId'], 'to_financial_account_id');
91 if ($values['to_financial_account_id']) {
92 $values['to_financial_account'] = CRM_Contribute_PseudoConstant::financialAccount($values['to_financial_account_id']);
93 }
94 $values['payment_processor_id'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialTrxn', $financialTrxnId['financialTrxnId'], 'payment_processor_id');
95 if ($values['payment_processor_id']) {
96 $values['payment_processor_name'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessor', $values['payment_processor_id'], 'name');
97 }
98 }
99
100 if (!empty($values['contribution_recur_id'])) {
101 $sql = "SELECT installments, frequency_interval, frequency_unit FROM civicrm_contribution_recur WHERE id = %1";
102 $params = [1 => [$values['contribution_recur_id'], 'Integer']];
103 $dao = CRM_Core_DAO::executeQuery($sql, $params);
104 if ($dao->fetch()) {
105 $values['recur_installments'] = $dao->installments;
106 $values['recur_frequency_unit'] = $dao->frequency_unit;
107 $values['recur_frequency_interval'] = $dao->frequency_interval;
108 }
109 }
110
111 try {
112 $participantLineItems = \Civi\Api4\LineItem::get()
113 ->addSelect('entity_id', 'participant.role_id:label', 'participant.fee_level', 'participant.contact_id', 'contact.display_name')
114 ->addJoin('Participant AS participant', 'LEFT', ['participant.id', '=', 'entity_id'])
115 ->addJoin('Contact AS contact', 'LEFT', ['contact.id', '=', 'participant.contact_id'])
116 ->addWhere('entity_table', '=', 'civicrm_participant')
117 ->addWhere('contribution_id', '=', $id)
118 ->execute();
119 }
120 catch (API_Exception $e) {
121 // likely don't have permission for events/participants
122 $participantLineItems = [];
123 }
124
125 $associatedParticipants = FALSE;
126 foreach ($participantLineItems as $participant) {
127 $associatedParticipants[] = [
128 'participantLink' => CRM_Utils_System::url('civicrm/contact/view/participant',
129 "action=view&reset=1&id={$participant['entity_id']}&cid={$participant['participant.contact_id']}&context=home"
130 ),
131 'participantName' => $participant['contact.display_name'],
132 'fee' => implode(', ', $participant['participant.fee_level']),
133 'role' => implode(', ', $participant['participant.role_id:label']),
134 ];
135 }
136 $this->assign('associatedParticipants', $associatedParticipants);
137
138 $groupTree = CRM_Core_BAO_CustomGroup::getTree('Contribution', NULL, $id, 0, $values['financial_type_id'] ?? NULL,
139 NULL, TRUE, NULL, FALSE, CRM_Core_Permission::VIEW);
140 CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, FALSE, NULL, NULL, NULL, $id);
141
142 $premiumId = NULL;
143 $dao = new CRM_Contribute_DAO_ContributionProduct();
144 $dao->contribution_id = $id;
145 if ($dao->find(TRUE)) {
146 $premiumId = $dao->id;
147 $productID = $dao->product_id;
148 }
149
150 $this->assign('premium', '');
151 if ($premiumId) {
152 $productDAO = new CRM_Contribute_DAO_Product();
153 $productDAO->id = $productID;
154 $productDAO->find(TRUE);
155
156 $this->assign('premium', $productDAO->name);
157 $this->assign('option', $dao->product_option);
158 $this->assign('fulfilled', $dao->fulfilled_date);
159 }
160
161 // Get Note
162 $noteValue = CRM_Core_BAO_Note::getNote(CRM_Utils_Array::value('id', $values), 'civicrm_contribution');
163 $values['note'] = array_values($noteValue);
164
165 // show billing address location details, if exists
166 $values['billing_address'] = '';
167 if (!empty($values['address_id'])) {
168 $addressParams = ['id' => $values['address_id']];
169 $addressDetails = CRM_Core_BAO_Address::getValues($addressParams, FALSE, 'id');
170 $addressDetails = array_values($addressDetails);
171 $values['billing_address'] = $addressDetails[0]['display'];
172 }
173
174 //assign soft credit record if exists.
175 $SCRecords = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($this->getID(), TRUE);
176 $this->assign('softContributions', empty($SCRecords['soft_credit']) ? NULL : $SCRecords['soft_credit']);
177 // unset doesn't complain if array member missing
178 unset($SCRecords['soft_credit']);
179 foreach ($SCRecords as $name => $value) {
180 $this->assign($name, $value);
181 }
182
183 $lineItems = [CRM_Price_BAO_LineItem::getLineItemsByContributionID(($id))];
184 $this->assign('lineItem', $lineItems);
185 $values['totalAmount'] = $values['total_amount'];
186 $this->assign('displayLineItemFinancialType', TRUE);
187
188 //do check for campaigns
189 $values['campaign'] = '';
190 if ($campaignId = CRM_Utils_Array::value('campaign_id', $values)) {
191 $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns($campaignId);
192 $values['campaign'] = $campaigns[$campaignId];
193 }
194 if ($contributionStatus === 'Refunded') {
195 $this->assign('refund_trxn_id', CRM_Core_BAO_FinancialTrxn::getRefundTransactionTrxnID($id));
196 }
197
198 // assign values to the template
199 $this->assignVariables($values, array_keys($values));
200 $invoicing = CRM_Invoicing_Utils::isInvoicingEnabled();
201 $this->assign('invoicing', $invoicing);
202 $this->assign('isDeferred', Civi::settings()->get('deferred_revenue_enabled'));
203 if ($invoicing && isset($values['tax_amount'])) {
204 $this->assign('totalTaxAmount', $values['tax_amount']);
205 }
206
207 // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container
208 $displayName = CRM_Contact_BAO_Contact::displayName($values['contact_id']);
209 $this->assign('displayName', $displayName);
210 // Check if this is default domain contact CRM-10482
211 if (CRM_Contact_BAO_Contact::checkDomainContact($values['contact_id'])) {
212 $displayName .= ' (' . ts('default organization') . ')';
213 }
214
215 if (empty($values['is_template'])) {
216 $this->setTitle(ts('View Contribution from') . ' ' . $displayName);
217 }
218 else {
219 $this->setTitle(ts('View Template Contribution from') . ' ' . $displayName);
220 }
221
222 // add viewed contribution to recent items list
223 $url = CRM_Utils_System::url('civicrm/contact/view/contribution',
224 "action=view&reset=1&id={$values['id']}&cid={$values['contact_id']}&context=home"
225 );
226
227 $title = $displayName . ' - (' . CRM_Utils_Money::format($values['total_amount'], $values['currency']) . ' ' . ' - ' . $values['financial_type'] . ')';
228
229 $recentOther = [];
230 if (CRM_Core_Permission::checkActionPermission('CiviContribute', CRM_Core_Action::UPDATE)) {
231 $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/view/contribution',
232 "action=update&reset=1&id={$values['id']}&cid={$values['contact_id']}&context=home"
233 );
234 }
235 if (CRM_Core_Permission::checkActionPermission('CiviContribute', CRM_Core_Action::DELETE)) {
236 $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/contact/view/contribution',
237 "action=delete&reset=1&id={$values['id']}&cid={$values['contact_id']}&context=home"
238 );
239 }
240 CRM_Utils_Recent::add($title,
241 $url,
242 $values['id'],
243 'Contribution',
244 $values['contact_id'],
245 NULL,
246 $recentOther
247 );
248 $statusOptionValueNames = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
249 $contributionStatus = $statusOptionValueNames[$values['contribution_status_id']];
250 $this->assign('addRecordPayment', in_array($contributionStatus, ['Partially paid', 'Pending refund', 'Pending']));
251 $this->assignPaymentInfoBlock($id);
252
253 $searchKey = NULL;
254 if ($this->controller->_key) {
255 $searchKey = $this->controller->_key;
256 }
257
258 if ($this->isHasAccess('update')) {
259 $urlParams = "reset=1&id={$id}&cid={$values['contact_id']}&action=update&context={$context}";
260 if (($context === 'fulltext' || $context === 'search') && $searchKey) {
261 $urlParams = "reset=1&id={$id}&cid={$values['contact_id']}&action=update&context={$context}&key={$searchKey}";
262 }
263 if (!$contribution['is_template']) {
264 foreach (CRM_Contribute_BAO_Contribution::getContributionPaymentLinks($this->getID(), $contributionStatus) as $paymentButton) {
265 $paymentButton['icon'] = 'fa-plus-circle';
266 $linkButtons[] = $paymentButton;
267 }
268 }
269 $linkButtons[] = [
270 'title' => ts('Edit'),
271 'url' => 'civicrm/contact/view/contribution',
272 'qs' => $urlParams,
273 'icon' => 'fa-pencil',
274 'accessKey' => 'e',
275 'ref' => '',
276 'name' => '',
277 'extra' => '',
278 ];
279 }
280
281 if ($this->isHasAccess('delete')) {
282 $urlParams = "reset=1&id={$id}&cid={$values['contact_id']}&action=delete&context={$context}";
283 if (($context === 'fulltext' || $context === 'search') && $searchKey) {
284 $urlParams = "reset=1&id={$id}&cid={$values['contact_id']}&action=delete&context={$context}&key={$searchKey}";
285 }
286 $linkButtons[] = [
287 'title' => ts('Delete'),
288 'url' => 'civicrm/contact/view/contribution',
289 'qs' => $urlParams,
290 'icon' => 'fa-trash',
291 'accessKey' => '',
292 'ref' => '',
293 'name' => '',
294 'extra' => '',
295 ];
296 }
297
298 $pdfUrlParams = "reset=1&id={$id}&cid={$values['contact_id']}";
299 $emailUrlParams = "reset=1&id={$id}&cid={$values['contact_id']}&select=email";
300 if (Civi::settings()->get('invoicing') && !$contribution['is_template']) {
301 if (($values['contribution_status'] !== 'Refunded') && ($values['contribution_status'] !== 'Cancelled')) {
302 $invoiceButtonText = ts('Download Invoice');
303 }
304 else {
305 $invoiceButtonText = ts('Download Invoice and Credit Note');
306 }
307 $linkButtons[] = [
308 'title' => $invoiceButtonText,
309 'url' => 'civicrm/contribute/invoice',
310 'qs' => $pdfUrlParams,
311 'icon' => 'fa-download',
312 ];
313 $linkButtons[] = [
314 'title' => ts('Email Invoice'),
315 'url' => 'civicrm/contribute/invoice/email',
316 'qs' => $emailUrlParams,
317 'icon' => 'fa-paper-plane',
318 ];
319 }
320 $this->assign('linkButtons', $linkButtons ?? []);
321 // These next 3 parameters are used to construct a url in PaymentInfo.tpl
322 $this->assign('contactId', $values['contact_id']);
323 $this->assign('componentId', $id);
324 $this->assign('component', 'contribution');
325 $this->assignPaymentInfoBlock($id);
326 }
327
328 /**
329 * Build the form object.
330 */
331 public function buildQuickForm() {
332 $this->addButtons([
333 [
334 'type' => 'cancel',
335 'name' => ts('Done'),
336 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
337 'isDefault' => TRUE,
338 ],
339 ]);
340 }
341
342 /**
343 * Assign the values to build the payment info block.
344 *
345 * @todo - this is a bit too much copy & paste from AbstractEditPayment
346 * (justifying on the basis it's 'pretty short' and in a different inheritance
347 * tree. I feel like traits are probably the longer term answer).
348 *
349 * @param int $id
350 *
351 * @return string
352 * Block title.
353 */
354 protected function assignPaymentInfoBlock($id) {
355 // component is used in getPaymentInfo primarily to retrieve the contribution id, we
356 // already have that.
357 $paymentInfo = CRM_Contribute_BAO_Contribution::getPaymentInfo($id, 'contribution', TRUE);
358 $title = ts('View Payment');
359 $this->assign('transaction', TRUE);
360 $this->assign('payments', $paymentInfo['transaction']);
361 $this->assign('paymentLinks', $paymentInfo['payment_links']);
362 return $title;
363 }
364
365 /**
366 * @param string $action
367 *
368 * @return bool
369 */
370 private function isHasAccess(string $action): bool {
371 try {
372 return Contribution::checkAccess()
373 ->setAction($action)
374 ->addValue('id', $this->getID())
375 ->execute()->first()['access'];
376 }
377 catch (API_Exception $e) {
378 return FALSE;
379 }
380 }
381
382 /**
383 * Get the contribution ID.
384 *
385 * @return int
386 */
387 private function getID(): int {
388 $id = $this->get('id');
389 if (empty($id)) {
390 CRM_Core_Error::statusBounce('Contribution ID is required');
391 }
392 return $id;
393 }
394
395 }