Merge pull request #21318 from michaelmcandrew/dev-core-2798
[civicrm-core.git] / CRM / Contribute / Form / UpdateSubscription.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 * @package CRM
14 * @copyright CiviCRM LLC https://civicrm.org/licensing
15 */
16
17 use Civi\Payment\Exception\PaymentProcessorException;
18
19 /**
20 * This class generates form components generic to recurring contributions.
21 *
22 * It delegates the work to lower level subclasses and integrates the changes
23 * back in. It also uses a lot of functionality with the CRM API's, so any change
24 * made here could potentially affect the API etc. Be careful, be aware, use unit tests.
25 */
26 class CRM_Contribute_Form_UpdateSubscription extends CRM_Contribute_Form_ContributionRecur {
27
28 protected $_subscriptionDetails = NULL;
29
30 public $_paymentProcessor = NULL;
31
32 public $_paymentProcessorObj = NULL;
33
34 /**
35 * Fields that affect the schedule and are defined as editable by the processor.
36 *
37 * @var array
38 */
39 protected $editableScheduleFields = [];
40
41 /**
42 * The id of the contact associated with this recurring contribution.
43 *
44 * @var int
45 */
46 public $_contactID;
47
48 /**
49 * Pre-processing for the form.
50 *
51 * @throws \Exception
52 */
53 public function preProcess() {
54
55 parent::preProcess();
56 $this->setAction(CRM_Core_Action::UPDATE);
57
58 if ($this->_coid) {
59 $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'info');
60 // @todo test & replace with $this->_paymentProcessorObj = Civi\Payment\System::singleton()->getById($this->_paymentProcessor['id']);
61 $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'obj');
62 $this->contributionRecurID = $this->_subscriptionDetails->recur_id;
63 }
64 elseif ($this->contributionRecurID) {
65 $this->_coid = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $this->contributionRecurID, 'id', 'contribution_recur_id');
66 }
67
68 if (!$this->contributionRecurID || !$this->_subscriptionDetails) {
69 CRM_Core_Error::statusBounce(ts('Required information missing.'));
70 }
71
72 if ($this->_subscriptionDetails->membership_id && $this->_subscriptionDetails->auto_renew) {
73 // Add Membership details to form
74 $membership = civicrm_api3('Membership', 'get', [
75 'contribution_recur_id' => $this->contributionRecurID,
76 ]);
77 if (!empty($membership['count'])) {
78 $membershipDetails = reset($membership['values']);
79 $values['membership_id'] = $membershipDetails['id'];
80 $values['membership_name'] = $membershipDetails['membership_name'];
81 }
82 $this->assign('recurMembership', $values);
83 $this->assign('contactId', $this->_subscriptionDetails->contact_id);
84 }
85
86 $this->assign('self_service', $this->isSelfService());
87
88 $this->editableScheduleFields = $this->_paymentProcessorObj->getEditableRecurringScheduleFields();
89
90 $changeHelpText = $this->_paymentProcessorObj->getRecurringScheduleUpdateHelpText();
91 if (!in_array('amount', $this->editableScheduleFields)) {
92 // Not sure if this is good behaviour - maintaining this existing behaviour for now.
93 CRM_Core_Session::setStatus($changeHelpText, ts('Warning'), 'alert');
94 }
95 else {
96 $this->assign('changeHelpText', $changeHelpText);
97 }
98 $alreadyHardCodedFields = ['amount', 'installments'];
99 foreach ($this->editableScheduleFields as $editableScheduleField) {
100 if (!in_array($editableScheduleField, $alreadyHardCodedFields)) {
101 $this->addField($editableScheduleField, ['entity' => 'ContributionRecur'], FALSE, FALSE);
102 }
103 }
104
105 // when custom data is included in this page
106 if (!empty($_POST['hidden_custom']) && !$this->isSelfService()) {
107 CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'ContributionRecur', $this->contributionRecurID);
108 CRM_Custom_Form_CustomData::buildQuickForm($this);
109 CRM_Custom_Form_CustomData::setDefaultValues($this);
110 }
111
112 $this->assign('editableScheduleFields', array_diff($this->editableScheduleFields, $alreadyHardCodedFields));
113
114 if ($this->_subscriptionDetails->contact_id) {
115 [$this->_donorDisplayName, $this->_donorEmail] = CRM_Contact_BAO_Contact::getContactDetails($this->_subscriptionDetails->contact_id);
116 }
117
118 $this->setTitle(ts('Update Recurring Contribution'));
119
120 // Handle context redirection.
121 CRM_Contribute_BAO_ContributionRecur::setSubscriptionContext();
122 }
123
124 /**
125 * Set default values for the form.
126 *
127 * Note that in edit/view mode the default values are retrieved from the database.
128 */
129 public function setDefaultValues() {
130 $this->_defaults = [];
131 $this->_defaults['amount'] = $this->_subscriptionDetails->amount;
132 $this->_defaults['installments'] = $this->_subscriptionDetails->installments;
133 $this->_defaults['campaign_id'] = $this->_subscriptionDetails->campaign_id;
134 $this->_defaults['financial_type_id'] = $this->_subscriptionDetails->financial_type_id;
135 foreach ($this->editableScheduleFields as $field) {
136 $this->_defaults[$field] = $this->_subscriptionDetails->$field ?? NULL;
137 }
138
139 return $this->_defaults;
140 }
141
142 /**
143 * Actually build the components of the form.
144 */
145 public function buildQuickForm() {
146 // CRM-16398: If current recurring contribution got > 1 lineitems then make amount field readonly
147 $amtAttr = ['size' => 20];
148 $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($this->_coid);
149 if (count($lineItems) > 1) {
150 $amtAttr += ['readonly' => TRUE];
151 }
152 $this->addMoney('amount', ts('Recurring Contribution Amount'), TRUE, $amtAttr,
153 TRUE, 'currency', $this->_subscriptionDetails->currency, TRUE
154 );
155
156 $this->add('text', 'installments', ts('Number of Installments'), ['size' => 20], FALSE);
157
158 if ($this->_donorEmail) {
159 $this->add('checkbox', 'is_notify', ts('Notify Contributor?'));
160 }
161
162 if (CRM_Core_Permission::check('edit contributions')) {
163 CRM_Campaign_BAO_Campaign::addCampaign($this, $this->_subscriptionDetails->campaign_id);
164 }
165
166 if (CRM_Contribute_BAO_ContributionRecur::supportsFinancialTypeChange($this->contributionRecurID)) {
167 $this->addEntityRef('financial_type_id', ts('Financial Type'), ['entity' => 'FinancialType'], !$this->isSelfService());
168 }
169
170 // Add custom data
171 $this->assign('customDataType', 'ContributionRecur');
172 $this->assign('entityID', $this->contributionRecurID);
173
174 $type = 'next';
175 if ($this->isSelfService()) {
176 $type = 'submit';
177 }
178
179 // define the buttons
180 $this->addButtons([
181 [
182 'type' => $type,
183 'name' => ts('Save'),
184 'isDefault' => TRUE,
185 ],
186 [
187 'type' => 'cancel',
188 'name' => ts('Cancel'),
189 ],
190 ]);
191 }
192
193 /**
194 * Called after the user submits the form.
195 */
196 public function postProcess() {
197 // store the submitted values in an array
198 $params = $this->exportValues();
199
200 if ($this->isSelfService() && $this->_donorEmail) {
201 // for self service force notify
202 $params['is_notify'] = 1;
203 }
204
205 // if this is an update of an existing recurring contribution, pass the ID
206 $params['id'] = $this->_subscriptionDetails->recur_id;
207 $message = '';
208
209 $params['subscriptionId'] = $this->getSubscriptionDetails()->processor_id;
210 $updateSubscription = TRUE;
211 if ($this->_paymentProcessorObj->supports('changeSubscriptionAmount')) {
212 try {
213 $updateSubscription = $this->_paymentProcessorObj->changeSubscriptionAmount($message, $params);
214 if ($updateSubscription instanceof CRM_Core_Error) {
215 CRM_Core_Error::deprecatedWarning('An exception should be thrown');
216 throw new PaymentProcessorException(ts('Could not update the Recurring contribution details'));
217 }
218 }
219 catch (PaymentProcessorException $e) {
220 CRM_Core_Error::statusBounce($e->getMessage());
221 }
222 }
223 if ($updateSubscription) {
224 // Handle custom data
225 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, $this->contributionRecurID, 'ContributionRecur');
226 // save the changes
227 CRM_Contribute_BAO_ContributionRecur::add($params);
228 $status = ts('Recurring contribution has been updated to: %1, every %2 %3(s) for %4 installments.',
229 [
230 1 => CRM_Utils_Money::format($params['amount'], $this->_subscriptionDetails->currency),
231 2 => $this->_subscriptionDetails->frequency_interval,
232 3 => $this->_subscriptionDetails->frequency_unit,
233 4 => $params['installments'],
234 ]
235 );
236
237 $msgTitle = ts('Update Success');
238 $msgType = 'success';
239 $msg = ts('Recurring Contribution Updated');
240 $contactID = $this->_subscriptionDetails->contact_id;
241
242 if ($this->_subscriptionDetails->amount != $params['amount']) {
243 $message .= "<br /> " . ts("Recurring contribution amount has been updated from %1 to %2 for this subscription.",
244 [
245 1 => CRM_Utils_Money::format($this->_subscriptionDetails->amount, $this->_subscriptionDetails->currency),
246 2 => CRM_Utils_Money::format($params['amount'], $this->_subscriptionDetails->currency),
247 ]) . ' ';
248 if ($this->_subscriptionDetails->amount < $params['amount']) {
249 $msg = ts('Recurring Contribution Updated - increased installment amount');
250 }
251 else {
252 $msg = ts('Recurring Contribution Updated - decreased installment amount');
253 }
254 }
255
256 if ($this->_subscriptionDetails->installments != $params['installments']) {
257 $message .= "<br /> " . ts("Recurring contribution installments have been updated from %1 to %2 for this subscription.", [
258 1 => $this->_subscriptionDetails->installments,
259 2 => $params['installments'],
260 ]) . ' ';
261 }
262
263 $activityParams = [
264 'source_contact_id' => $contactID,
265 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Update Recurring Contribution'),
266 'subject' => $msg,
267 'details' => $message,
268 'activity_date_time' => date('YmdHis'),
269 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'),
270 ];
271
272 $session = CRM_Core_Session::singleton();
273 $cid = $session->get('userID');
274
275 if ($cid) {
276 $activityParams['target_contact_id'][] = $activityParams['source_contact_id'];
277 $activityParams['source_contact_id'] = $cid;
278 }
279 CRM_Activity_BAO_Activity::create($activityParams);
280
281 if (!empty($params['is_notify'])) {
282 // send notification
283 if ($this->_subscriptionDetails->contribution_page_id) {
284 CRM_Core_DAO::commonRetrieveAll('CRM_Contribute_DAO_ContributionPage', 'id',
285 $this->_subscriptionDetails->contribution_page_id, $value, [
286 'title',
287 'receipt_from_name',
288 'receipt_from_email',
289 ]
290 );
291 $receiptFrom = '"' . CRM_Utils_Array::value('receipt_from_name', $value[$this->_subscriptionDetails->contribution_page_id]) . '" <' . $value[$this->_subscriptionDetails->contribution_page_id]['receipt_from_email'] . '>';
292 }
293 else {
294 $domainValues = CRM_Core_BAO_Domain::getNameAndEmail();
295 $receiptFrom = "$domainValues[0] <$domainValues[1]>";
296 }
297
298 [$donorDisplayName, $donorEmail] = CRM_Contact_BAO_Contact::getContactDetails($contactID);
299
300 $tplParams = [
301 'recur_frequency_interval' => $this->_subscriptionDetails->frequency_interval,
302 'recur_frequency_unit' => $this->_subscriptionDetails->frequency_unit,
303 'amount' => CRM_Utils_Money::format($params['amount']),
304 'installments' => $params['installments'],
305 ];
306
307 $tplParams['contact'] = ['display_name' => $donorDisplayName];
308 $tplParams['receipt_from_email'] = $receiptFrom;
309
310 $sendTemplateParams = [
311 'groupName' => 'msg_tpl_workflow_contribution',
312 'valueName' => 'contribution_recurring_edit',
313 'contactId' => $contactID,
314 'tplParams' => $tplParams,
315 'isTest' => $this->_subscriptionDetails->is_test,
316 'PDFFilename' => 'receipt.pdf',
317 'from' => $receiptFrom,
318 'toName' => $donorDisplayName,
319 'toEmail' => $donorEmail,
320 ];
321 [$sent] = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
322 }
323 }
324
325 $session = CRM_Core_Session::singleton();
326 $userID = $session->get('userID');
327 if ($userID && $status) {
328 CRM_Core_Session::setStatus($status, $msgTitle, $msgType);
329 }
330 elseif (!$userID) {
331 if ($status) {
332 CRM_Utils_System::setUFMessage($status);
333 }
334 // keep result as 1, since we not displaying anything on the redirected page anyway
335 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/subscriptionstatus',
336 "reset=1&task=update&result=1"));
337 }
338 }
339
340 }