Passed
Pull Request — development (#3829)
by Spuds
10:19
created

_set_payment_gateway_context()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
nc 4
nop 2
dl 0
loc 17
rs 10
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * Handles paid subscriptions on a user's profile.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 Beta 1
14
 *
15
 */
16
17
namespace ElkArte\Profile;
18
19
use ElkArte\AbstractController;
20
use ElkArte\Exceptions\Exception;
21
use ElkArte\Helper\Util;
22
use ElkArte\Languages\Txt;
23
24
/**
25
 * This class handles the paid subscriptions on a user's profile.
26
 */
27
class ProfileSubscriptions extends AbstractController
28
{
29
	/** @var array Holds the details of the subscription order */
30
	private $_order;
31
32
	/** @var array Holds all the available gateways so they can be initialized */
33
	private $_gateways;
34
35
	/** @var int The id of the subscription */
36
	private $_id_sub;
37
38
	/**
39
	 * Default action for the controller
40
	 *
41
	 * - This is just a stub as action_subscriptions is called from a menu pick
42
	 * and not routed through this method.
43
	 *
44
	 * @see AbstractController::action_index()
45
	 */
46
	public function action_index()
47
	{
48
		// $this->action_subscriptions();
49
	}
50
51
	/**
52
	 * Method for doing all the paid subscription stuff - kinda.
53
	 */
54
	public function action_subscriptions(): void
55
	{
56
		global $context, $txt;
57
58
		// Load the paid template anyway.
59
		theme()->getTemplates()->load('ManagePaid');
60
		Txt::load('ManagePaid');
61
62
		$memID = currentMemberID();
63
		$context['member']['id'] = $memID;
64
65
		// Load all the subscriptions in the system (loads in to $context)
66
		require_once(SUBSDIR . '/PaidSubscriptions.subs.php');
67
		loadSubscriptions();
68
69
		// Remove any invalid ones, ones not properly set up
70
		$this->_remove_invalid();
71
72
		// Work out what payment gateways are enabled.
73
		$this->_gateways = loadPaymentGateways();
74
		foreach ($this->_gateways as $id => $gateway)
75
		{
76
			$this->_gateways[$id] = new $gateway['display_class']();
77
78
			if (!$this->_gateways[$id]->gatewayEnabled())
79
			{
80
				unset($this->_gateways[$id]);
81
			}
82
		}
83
84
		// No gateways yet, no way to pay then, blame the admin!
85
		if (empty($this->_gateways))
86
		{
87
			throw new Exception($txt['paid_admin_not_setup_gateway']);
88
		}
89
90
		// Get the members' current subscriptions.
91
		$context['current'] = loadMemberSubscriptions($memID, $context['subscriptions']);
92
93
		// Find the active subscribed ones
94
		foreach ($context['current'] as $id => $current)
95
		{
96
			if ((int) $current['status'] === 1)
97
			{
98
				$context['subscriptions'][$id]['subscribed'] = true;
99
			}
100
		}
101
102
		// Simple "done"?
103
		if (isset($this->_req->query->done))
104
		{
105
			$this->_orderDone($memID);
106
		}
107
		// They have selected a subscription to order.
108
		elseif (isset($this->_req->query->confirm, $this->_req->post->sub_id) && is_array($this->_req->post->sub_id))
109
		{
110
			$this->_confirmOrder($memID);
111
		}
112
		// Show the users what's available and what they have
113
		else
114
		{
115
			$context['sub_template'] = 'user_subscription';
116
		}
117
	}
118
119
	/**
120
	 * Removes any subscriptions that are found to be invalid
121
	 *
122
	 * - Invalid defined by missing cost or missing period
123
	 */
124
	private function _remove_invalid(): void
125
	{
126
		global $context;
127
128
		foreach ($context['subscriptions'] as $id => $sub)
129
		{
130
			// Work out the costs.
131
			$costs = Util::unserialize($sub['real_cost']);
132
133
			$cost_array = [];
134
135
			// Flexible cost to time?
136
			if ($sub['real_length'] === 'F')
137
			{
138
				foreach ($costs as $duration => $cost)
139
				{
140
					if ((int) $cost !== 0)
141
					{
142
						$cost_array[$duration] = $cost;
143
					}
144
				}
145
			}
146
			else
147
			{
148
				$cost_array['fixed'] = $costs['fixed'];
149
			}
150
151
			// No cost associated with it, then drop it
152
			if (empty($cost_array))
153
			{
154
				unset($context['subscriptions'][$id]);
155
			}
156
			else
157
			{
158
				$context['subscriptions'][$id]['member'] = 0;
159
				$context['subscriptions'][$id]['subscribed'] = false;
160
				$context['subscriptions'][$id]['costs'] = $cost_array;
161
			}
162
		}
163
	}
164
165
	/**
166
	 * When the chosen payment gateway is done, and it supports a receipt link url,
167
	 * it will be set to come here.
168
	 *
169
	 * - This is different from the notification processing url which will point to subscriptions.php
170
	 * - Accessed by ?action=profile;u=123;area=subscriptions;sub_id=?;done
171
	 *
172
	 * @param int $memID
173
	 */
174
	private function _orderDone($memID): void
175
	{
176
		global $context;
177
178
		$sub_id = (int) $this->_req->query->sub_id;
179
180
		// Must exist, but let's be sure...
181
		if (isset($context['current'][$sub_id]))
182
		{
183
			// What are the pending details?
184
			$current_pending = Util::unserialize($context['current'][$sub_id]['pending_details']);
185
186
			// Nothing pending, nothing to do
187
			if (!empty($current_pending))
188
			{
189
				$current_pending = array_reverse($current_pending);
190
				foreach ($current_pending as $id => $sub)
191
				{
192
					// Just find one and change it to payback
193
					if ($sub[0] == $sub_id && trim($sub[3]) === 'prepay')
194
					{
195
						$current_pending[$id][3] = 'payback';
196
						break;
197
					}
198
				}
199
200
				// Save the details back.
201
				$pending_details = serialize($current_pending);
202
				updatePendingStatus($context['current'][$sub_id]['id'], $memID, $pending_details);
203
			}
204
		}
205
206
		// Say thank you
207
		$context['sub_template'] = 'paid_done';
208
	}
209
210
	/**
211
	 * Called when the user selects "Order" from the subscription page
212
	 *
213
	 * - Accessed with ?action=profile;u=123;area=subscriptions;confirm
214
	 *
215
	 * @param int $memID The id of the member who is ordering
216
	 *
217
	 * @throws Exception paid_sub_not_active
218
	 */
219
	private function _confirmOrder($memID): void
220
	{
221
		global $context, $modSettings, $txt;
222
223
		// Hopefully just one, if not we use the last one.
224
		foreach ($this->_req->post->sub_id as $k => $v)
225
		{
226
			$this->_id_sub = (int) $k;
227
		}
228
229
		// Selecting a subscription that does not exist or is not active?
230
		if ($this->_id_sub === null || $context['subscriptions'][$this->_id_sub]['active'] == 0)
231
		{
232
			throw new Exception('paid_sub_not_active');
233
		}
234
235
		// Simplify...
236
		$this->_order = $context['subscriptions'][$this->_id_sub];
237
238
		// Set the period in case this is a flex time frame.
239
		$period = 'xx';
240
		if ($this->_order['flexible'])
241
		{
242
			$cur = $this->_req->post->cur[$this->_id_sub];
243
			$period = isset($cur, $this->_order['costs'][$cur]) ? $cur : 'xx';
244
		}
245
246
		// Check we have a valid cost.
247
		if ($this->_order['flexible'] && $period === 'xx')
248
		{
249
			throw new Exception('paid_sub_not_active');
250
		}
251
252
		// Sort out the cost/currency.
253
		$context['currency'] = $modSettings['paid_currency_code'];
254
		$context['recur'] = $this->_order['repeatable'];
255
256
		// Payment details based on one time or flex
257
		$this->_set_value_cost_context();
258
259
		// Set up all the payment gateway context.
260
		$this->_set_payment_gateway_context($memID, $period);
261
262
		// No active payment gateways, then no way to pay, time to bail out, blame the admin
263
		if (empty($context['gateways']))
264
		{
265
			throw new Exception($txt['paid_admin_not_setup_gateway']);
266
		}
267
268
		// Now we are going to assume they want to take this out ;)
269
		$new_data = [$this->_order['id'], $context['value'], $period, 'prepay'];
270
271
		// They have one of these already?
272
		if (isset($context['current'][$this->_order['id']]))
273
		{
274
			// What are the details like?
275
			$current_pending = [];
276
			if ($context['current'][$this->_order['id']]['pending_details'] !== '')
277
			{
278
				$current_pending = Util::unserialize($context['current'][$this->_order['id']]['pending_details']);
279
			}
280
281
			// Don't get silly.
282
			if (count($current_pending) > 9)
0 ignored issues
show
Bug introduced by
It seems like $current_pending can also be of type false; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

282
			if (count(/** @scrutinizer ignore-type */ $current_pending) > 9)
Loading history...
283
			{
284
				$current_pending = [];
285
			}
286
287
			// Only record real pending payments as will otherwise confuse the admin!
288
			$pending_count = 0;
289
			foreach ($current_pending as $pending)
290
			{
291
				if (trim($pending[3]) === 'payback')
292
				{
293
					$pending_count++;
294
				}
295
			}
296
297
			// If it's already pending, don't increase the pending count
298
			if (!in_array($new_data, $current_pending))
0 ignored issues
show
Bug introduced by
It seems like $current_pending can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

298
			if (!in_array($new_data, /** @scrutinizer ignore-type */ $current_pending))
Loading history...
299
			{
300
				$current_pending[] = $new_data;
301
				$pending_details = serialize($current_pending);
302
				updatePendingSubscriptionCount($pending_count, $context['current'][$this->_order['id']]['id'], $memID, $pending_details);
303
			}
304
		}
305
		// Never had this before, lovely.
306
		else
307
		{
308
			$pending_details = serialize([$new_data]);
309
			logNewSubscription($this->_order['id'], $memID, $pending_details);
310
		}
311
312
		// Change the template.
313
		$context['sub'] = $this->_order;
314
		$context['sub_template'] = 'choose_payment';
315
	}
316
317
	/**
318
	 * Sets the value/cost/period/unit of the chosen order for use in templates
319
	 */
320
	private function _set_value_cost_context(): void
321
	{
322
		global $context, $modSettings, $txt;
323
324
		if ($this->_order['flexible'])
325
		{
326
			// Real cost...
327
			$context['value'] = $this->_order['costs'][$this->_req->post->cur[$this->_id_sub]];
328
			$context['cost'] = sprintf($modSettings['paid_currency_symbol'], $context['value']) . '/' . $txt[$this->_req->post->cur[$this->_id_sub]];
329
330
			// The period value for PayPal.
331
			$context['paypal_period'] = strtoupper(substr($this->_req->post->cur[$this->_id_sub], 0, 1));
332
		}
333
		else
334
		{
335
			// Real cost...
336
			$context['value'] = $this->_order['costs']['fixed'];
337
			$context['cost'] = sprintf($modSettings['paid_currency_symbol'], $context['value']);
338
339
			// Recur?
340
			preg_match('~(\d*)(\w)~', $this->_order['real_length'], $match);
341
			$context['paypal_unit'] = $match[1];
342
			$context['paypal_period'] = $match[2];
343
		}
344
	}
345
346
	/**
347
	 * Sets the required payment form fields for the various payment gateways
348
	 *
349
	 * @param int $memID The id of the member who is ordering
350
	 * @param string $period xx for none or a value of time
351
	 */
352
	private function _set_payment_gateway_context($memID, $period): void
353
	{
354
		global $context, $scripturl;
355
356
		$context['gateways'] = [];
357
		foreach (array_keys($this->_gateways) as $id)
358
		{
359
			$fields = $this->_gateways[$id]->fetchGatewayFields($this->_order['id'] . '+' . $memID, $this->_order, $context['value'], $period, $scripturl . '?action=profile&u=' . $memID . '&area=subscriptions&sub_id=' . $this->_order['id'] . '&done');
360
361
			if (!empty($fields['form']))
362
			{
363
				$context['gateways'][] = $fields;
364
365
				// Does this gateway have any JavaScript?
366
				if (!empty($fields['javascript']))
367
				{
368
					theme()->addInlineJavascript($fields['javascript'], true);
369
				}
370
			}
371
		}
372
	}
373
}
374