Merge pull request #12615 from eileenmcnaughton/amex
[civicrm-core.git] / CRM / Contribute / Form / UpdateSubscription.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
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-2018
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_Core_Form {
41
42 /**
43 * The recurring contribution id, used when editing the recurring contribution.
44 *
45 * @var int
46 */
47 protected $contributionRecurID = NULL;
48
49 protected $_coid = NULL;
50
51 protected $_subscriptionDetails = NULL;
52
53 protected $_selfService = FALSE;
54
55 public $_paymentProcessor = NULL;
56
57 public $_paymentProcessorObj = NULL;
58
59 /**
60 * Fields that affect the schedule and are defined as editable by the processor.
61 *
62 * @var array
63 */
64 protected $editableScheduleFields = array();
65
66 /**
67 * The id of the contact associated with this recurring contribution.
68 *
69 * @var int
70 */
71 public $_contactID;
72
73 /**
74 * Pre-processing for the form.
75 *
76 * @throws \Exception
77 */
78 public function preProcess() {
79
80 $this->setAction(CRM_Core_Action::UPDATE);
81
82 $this->contributionRecurID = CRM_Utils_Request::retrieve('crid', 'Integer', $this, FALSE);
83 if ($this->contributionRecurID) {
84 try {
85 $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getPaymentProcessorForRecurringContribution($this->contributionRecurID);
86 }
87 catch (CRM_Core_Exception $e) {
88 CRM_Core_Error::statusBounce(ts('There is no valid processor for this subscription so it cannot be edited.'));
89 }
90 catch (CiviCRM_API3_Exception $e) {
91 CRM_Core_Error::statusBounce(ts('There is no valid processor for this subscription so it cannot be edited.'));
92 }
93 $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->contributionRecurID);
94 }
95
96 $this->_coid = CRM_Utils_Request::retrieve('coid', 'Integer', $this, FALSE);
97 if ($this->_coid) {
98 $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'info');
99 // @todo test & replace with $this->_paymentProcessorObj = Civi\Payment\System::singleton()->getById($this->_paymentProcessor['id']);
100 $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'obj');
101 $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_coid, 'contribution');
102 $this->contributionRecurID = $this->_subscriptionDetails->recur_id;
103 }
104 elseif ($this->contributionRecurID) {
105 $this->_coid = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $this->contributionRecurID, 'id', 'contribution_recur_id');
106 }
107
108 if (!$this->contributionRecurID || !$this->_subscriptionDetails) {
109 CRM_Core_Error::statusBounce(ts('Required information missing.'));
110 }
111
112 if ($this->_subscriptionDetails->membership_id && $this->_subscriptionDetails->auto_renew) {
113 // Add Membership details to form
114 $membership = civicrm_api3('Membership', 'get', array(
115 'contribution_recur_id' => $this->contributionRecurID,
116 ));
117 if (!empty($membership['count'])) {
118 $membershipDetails = reset($membership['values']);
119 $values['membership_id'] = $membershipDetails['id'];
120 $values['membership_name'] = $membershipDetails['membership_name'];
121 }
122 $this->assign('recurMembership', $values);
123 $this->assign('contactId', $this->_subscriptionDetails->contact_id);
124 }
125
126 if (!CRM_Core_Permission::check('edit contributions')) {
127 $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this, FALSE);
128 if (!CRM_Contact_BAO_Contact_Utils::validChecksum($this->_subscriptionDetails->contact_id, $userChecksum)) {
129 CRM_Core_Error::statusBounce(ts('You do not have permission to update subscription.'));
130 }
131 $this->_selfService = TRUE;
132 }
133 $this->assign('self_service', $this->_selfService);
134
135 $this->editableScheduleFields = $this->_paymentProcessorObj->getEditableRecurringScheduleFields();
136
137 $changeHelpText = $this->_paymentProcessorObj->getRecurringScheduleUpdateHelpText();
138 if (!in_array('amount', $this->editableScheduleFields)) {
139 // Not sure if this is good behaviour - maintaining this existing behaviour for now.
140 CRM_Core_Session::setStatus($changeHelpText, ts('Warning'), 'alert');
141 }
142 else {
143 $this->assign('changeHelpText', $changeHelpText);
144 }
145 $alreadyHardCodedFields = array('amount', 'installments');
146 foreach ($this->editableScheduleFields as $editableScheduleField) {
147 if (!in_array($editableScheduleField, $alreadyHardCodedFields)) {
148 $this->addField($editableScheduleField, array('entity' => 'ContributionRecur'), FALSE, FALSE);
149 }
150 }
151
152 // when custom data is included in this page
153 if (!empty($_POST['hidden_custom'])) {
154 CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'ContributionRecur', $this->contributionRecurID);
155 CRM_Custom_Form_CustomData::buildQuickForm($this);
156 CRM_Custom_Form_CustomData::setDefaultValues($this);
157 }
158
159 $this->assign('editableScheduleFields', array_diff($this->editableScheduleFields, $alreadyHardCodedFields));
160
161 if ($this->_subscriptionDetails->contact_id) {
162 list($this->_donorDisplayName, $this->_donorEmail) = CRM_Contact_BAO_Contact::getContactDetails($this->_subscriptionDetails->contact_id);
163 }
164
165 CRM_Utils_System::setTitle(ts('Update Recurring Contribution'));
166
167 // Handle context redirection.
168 CRM_Contribute_BAO_ContributionRecur::setSubscriptionContext();
169 }
170
171 /**
172 * Set default values for the form.
173 *
174 * Note that in edit/view mode the default values are retrieved from the database.
175 */
176 public function setDefaultValues() {
177 $this->_defaults = array();
178 $this->_defaults['amount'] = $this->_subscriptionDetails->amount;
179 $this->_defaults['installments'] = $this->_subscriptionDetails->installments;
180 $this->_defaults['campaign_id'] = $this->_subscriptionDetails->campaign_id;
181 $this->_defaults['financial_type_id'] = $this->_subscriptionDetails->financial_type_id;
182 $this->_defaults['is_notify'] = 1;
183 foreach ($this->editableScheduleFields as $field) {
184 $this->_defaults[$field] = isset($this->_subscriptionDetails->$field) ? $this->_subscriptionDetails->$field : NULL;
185 }
186
187 return $this->_defaults;
188 }
189
190 /**
191 * Actually build the components of the form.
192 */
193 public function buildQuickForm() {
194 // CRM-16398: If current recurring contribution got > 1 lineitems then make amount field readonly
195 $amtAttr = array('size' => 20);
196 $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($this->_coid);
197 if (count($lineItems) > 1) {
198 $amtAttr += array('readonly' => TRUE);
199 }
200 $this->addMoney('amount', ts('Recurring Contribution Amount'), TRUE, $amtAttr,
201 TRUE, 'currency', $this->_subscriptionDetails->currency, TRUE
202 );
203
204 $this->add('text', 'installments', ts('Number of Installments'), array('size' => 20), FALSE);
205
206 if ($this->_donorEmail) {
207 $this->add('checkbox', 'is_notify', ts('Notify Contributor?'));
208 }
209
210 if (CRM_Core_Permission::check('edit contributions')) {
211 CRM_Campaign_BAO_Campaign::addCampaign($this, $this->_subscriptionDetails->campaign_id);
212 }
213
214 if (CRM_Contribute_BAO_ContributionRecur::supportsFinancialTypeChange($this->contributionRecurID)) {
215 $this->addEntityRef('financial_type_id', ts('Financial Type'), array('entity' => 'FinancialType'), !$this->_selfService);
216 }
217
218 // Add custom data
219 $this->assign('customDataType', 'ContributionRecur');
220 $this->assign('entityID', $this->contributionRecurID);
221
222 $type = 'next';
223 if ($this->_selfService) {
224 $type = 'submit';
225 }
226
227 // define the buttons
228 $this->addButtons(array(
229 array(
230 'type' => $type,
231 'name' => ts('Save'),
232 'isDefault' => TRUE,
233 ),
234 array(
235 'type' => 'cancel',
236 'name' => ts('Cancel'),
237 ),
238 )
239 );
240 }
241
242 /**
243 * Called after the user submits the form.
244 */
245 public function postProcess() {
246 // store the submitted values in an array
247 $params = $this->exportValues();
248
249 if ($this->_selfService && $this->_donorEmail) {
250 // for self service force notify
251 $params['is_notify'] = 1;
252 }
253
254 // if this is an update of an existing recurring contribution, pass the ID
255 $params['id'] = $this->_subscriptionDetails->recur_id;
256 $message = '';
257
258 $params['subscriptionId'] = $this->_subscriptionDetails->subscription_id;
259 $updateSubscription = TRUE;
260 if ($this->_paymentProcessorObj->supports('changeSubscriptionAmount')) {
261 $updateSubscription = $this->_paymentProcessorObj->changeSubscriptionAmount($message, $params);
262 }
263 if (is_a($updateSubscription, 'CRM_Core_Error')) {
264 CRM_Core_Error::displaySessionError($updateSubscription);
265 $status = ts('Could not update the Recurring contribution details');
266 $msgTitle = ts('Update Error');
267 $msgType = 'error';
268 }
269 elseif ($updateSubscription) {
270 // Handle custom data
271 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, $this->contributionRecurID, 'ContributionRecur');
272 // save the changes
273 CRM_Contribute_BAO_ContributionRecur::add($params);
274 $status = ts('Recurring contribution has been updated to: %1, every %2 %3(s) for %4 installments.',
275 array(
276 1 => CRM_Utils_Money::format($params['amount'], $this->_subscriptionDetails->currency),
277 2 => $this->_subscriptionDetails->frequency_interval,
278 3 => $this->_subscriptionDetails->frequency_unit,
279 4 => $params['installments'],
280 )
281 );
282
283 $msgTitle = ts('Update Success');
284 $msgType = 'success';
285 $msg = ts('Recurring Contribution Updated');
286 $contactID = $this->_subscriptionDetails->contact_id;
287
288 if ($this->_subscriptionDetails->amount != $params['amount']) {
289 $message .= "<br /> " . ts("Recurring contribution amount has been updated from %1 to %2 for this subscription.",
290 array(
291 1 => CRM_Utils_Money::format($this->_subscriptionDetails->amount, $this->_subscriptionDetails->currency),
292 2 => CRM_Utils_Money::format($params['amount'], $this->_subscriptionDetails->currency),
293 )) . ' ';
294 if ($this->_subscriptionDetails->amount < $params['amount']) {
295 $msg = ts('Recurring Contribution Updated - increased installment amount');
296 }
297 else {
298 $msg = ts('Recurring Contribution Updated - decreased installment amount');
299 }
300 }
301
302 if ($this->_subscriptionDetails->installments != $params['installments']) {
303 $message .= "<br /> " . ts("Recurring contribution installments have been updated from %1 to %2 for this subscription.", array(
304 1 => $this->_subscriptionDetails->installments,
305 2 => $params['installments'],
306 )) . ' ';
307 }
308
309 $activityParams = array(
310 'source_contact_id' => $contactID,
311 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Update Recurring Contribution'),
312 'subject' => $msg,
313 'details' => $message,
314 'activity_date_time' => date('YmdHis'),
315 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'),
316 );
317
318 $session = CRM_Core_Session::singleton();
319 $cid = $session->get('userID');
320
321 if ($cid) {
322 $activityParams['target_contact_id'][] = $activityParams['source_contact_id'];
323 $activityParams['source_contact_id'] = $cid;
324 }
325 CRM_Activity_BAO_Activity::create($activityParams);
326
327 if (!empty($params['is_notify'])) {
328 // send notification
329 if ($this->_subscriptionDetails->contribution_page_id) {
330 CRM_Core_DAO::commonRetrieveAll('CRM_Contribute_DAO_ContributionPage', 'id',
331 $this->_subscriptionDetails->contribution_page_id, $value, array(
332 'title',
333 'receipt_from_name',
334 'receipt_from_email',
335 )
336 );
337 $receiptFrom = '"' . CRM_Utils_Array::value('receipt_from_name', $value[$this->_subscriptionDetails->contribution_page_id]) . '" <' . $value[$this->_subscriptionDetails->contribution_page_id]['receipt_from_email'] . '>';
338 }
339 else {
340 $domainValues = CRM_Core_BAO_Domain::getNameAndEmail();
341 $receiptFrom = "$domainValues[0] <$domainValues[1]>";
342 }
343
344 list($donorDisplayName, $donorEmail) = CRM_Contact_BAO_Contact::getContactDetails($contactID);
345
346 $tplParams = array(
347 'recur_frequency_interval' => $this->_subscriptionDetails->frequency_interval,
348 'recur_frequency_unit' => $this->_subscriptionDetails->frequency_unit,
349 'amount' => CRM_Utils_Money::format($params['amount']),
350 'installments' => $params['installments'],
351 );
352
353 $tplParams['contact'] = array('display_name' => $donorDisplayName);
354 $tplParams['receipt_from_email'] = $receiptFrom;
355
356 $sendTemplateParams = array(
357 'groupName' => 'msg_tpl_workflow_contribution',
358 'valueName' => 'contribution_recurring_edit',
359 'contactId' => $contactID,
360 'tplParams' => $tplParams,
361 'isTest' => $this->_subscriptionDetails->is_test,
362 'PDFFilename' => 'receipt.pdf',
363 'from' => $receiptFrom,
364 'toName' => $donorDisplayName,
365 'toEmail' => $donorEmail,
366 );
367 list($sent) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
368 }
369 }
370
371 $session = CRM_Core_Session::singleton();
372 $userID = $session->get('userID');
373 if ($userID && $status) {
374 CRM_Core_Session::setStatus($status, $msgTitle, $msgType);
375 }
376 elseif (!$userID) {
377 if ($status) {
378 CRM_Utils_System::setUFMessage($status);
379 }
380 // keep result as 1, since we not displaying anything on the redirected page anyway
381 return CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/subscriptionstatus',
382 "reset=1&task=update&result=1"));
383 }
384 }
385
386 /**
387 * Explicitly declare the form context.
388 */
389 public function getDefaultContext() {
390 return 'create';
391 }
392
393 }