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