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