Passed
Push — develop-3.3.x ( 5a7b5f...c85e2c )
by Mario
02:35
created

transactions_controller::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 44
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 23
nc 1
nop 18
dl 0
loc 44
rs 9.552
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 *
4
 * PayPal Donation extension for the phpBB Forum Software package.
5
 *
6
 * @copyright (c) 2015-2024 Skouat
7
 * @license GNU General Public License, version 2 (GPL-2.0)
8
 *
9
 */
10
11
namespace skouat\ppde\controller\admin;
12
13
use phpbb\auth\auth;
14
use phpbb\config\config;
15
use phpbb\language\language;
16
use phpbb\log\log;
17
use phpbb\request\request;
18
use phpbb\template\template;
19
use phpbb\user;
20
use phpbb\user_loader;
21
use skouat\ppde\actions\core;
22
use skouat\ppde\actions\currency;
23
use skouat\ppde\exception\transaction_exception;
24
use skouat\ppde\operators\transactions;
25
use Symfony\Component\DependencyInjection\ContainerInterface;
26
27
/**
28
 * @property array              args               Array of args for hidden fields
29
 * @property config             config             Config object
30
 * @property ContainerInterface container          Service container interface
31
 * @property string             id_prefix_name     Prefix name for identifier in the URL
32
 * @property string             lang_key_prefix    Prefix for the messages thrown by exceptions
33
 * @property language           language           Language user object
34
 * @property log                log                The phpBB log system
35
 * @property string             module_name        Name of the module currently used
36
 * @property request            request            Request object
37
 * @property bool               submit             State of submit $_POST variable
38
 * @property template           template           Template object
39
 * @property string             u_action           Action URL
40
 * @property user               user               User object
41
 * @property user_loader        user_loader        User loader object
42
 */
43
class transactions_controller extends admin_main
44
{
45
	public $ppde_operator;
46
	protected $adm_relative_path;
47
	protected $auth;
48
	protected $user_loader;
49
	protected $entry_count;
50
	protected $last_page_offset;
51
	protected $php_ext;
52
	protected $phpbb_admin_path;
53
	protected $phpbb_root_path;
54
	protected $ppde_actions;
55
	protected $ppde_actions_currency;
56
	protected $ppde_entity;
57
	protected $table_prefix;
58
	protected $table_ppde_transactions;
59
60
	/**
61
	 * Constructor
62
	 *
63
	 * @param auth                             $auth                       Authentication object
64
	 * @param config                           $config                     Config object
65
	 * @param ContainerInterface               $container                  Service container interface
66
	 * @param language                         $language                   Language user object
67
	 * @param log                              $log                        The phpBB log system
68
	 * @param core                             $ppde_actions               PPDE actions object
69
	 * @param currency                         $ppde_actions_currency      PPDE currency actions object
70
	 * @param \skouat\ppde\entity\transactions $ppde_entity_transactions   Entity object
71
	 * @param transactions                     $ppde_operator_transactions Operator object
72
	 * @param request                          $request                    Request object
73
	 * @param template                         $template                   Template object
74
	 * @param user                             $user                       User object
75
	 * @param user_loader                      $user_loader                User loader object
76
	 * @param string                           $adm_relative_path          phpBB admin relative path
77
	 * @param string                           $phpbb_root_path            phpBB root path
78
	 * @param string                           $php_ext                    phpEx
79
	 * @param string                           $table_prefix               The table prefix
80
	 * @param string                           $table_ppde_transactions    Name of the table used to store data
81
	 */
82
	public function __construct(
83
		auth $auth,
84
		config $config,
85
		ContainerInterface $container,
86
		language $language,
87
		log $log,
88
		core $ppde_actions,
89
		currency $ppde_actions_currency,
90
		\skouat\ppde\entity\transactions $ppde_entity_transactions,
91
		transactions $ppde_operator_transactions,
92
		request $request,
93
		template $template,
94
		user $user,
95
		user_loader $user_loader,
96
		string $adm_relative_path,
97
		string $phpbb_root_path,
98
		string $php_ext,
99
		string $table_prefix,
100
		string $table_ppde_transactions
101
	)
102
	{
103
		$this->auth = $auth;
104
		$this->config = $config;
105
		$this->container = $container;
106
		$this->language = $language;
107
		$this->log = $log;
108
		$this->ppde_actions = $ppde_actions;
109
		$this->ppde_actions_currency = $ppde_actions_currency;
110
		$this->ppde_entity = $ppde_entity_transactions;
111
		$this->ppde_operator = $ppde_operator_transactions;
112
		$this->request = $request;
113
		$this->template = $template;
114
		$this->user = $user;
115
		$this->user_loader = $user_loader;
116
		$this->adm_relative_path = $adm_relative_path;
117
		$this->phpbb_admin_path = $phpbb_root_path . $adm_relative_path;
118
		$this->phpbb_root_path = $phpbb_root_path;
119
		$this->php_ext = $php_ext;
120
		$this->table_prefix = $table_prefix;
121
		$this->table_ppde_transactions = $table_ppde_transactions;
122
		parent::__construct(
123
			'transactions',
124
			'PPDE_DT',
125
			'transaction'
126
		);
127
	}
128
129
	/**
130
	 * {@inheritdoc}
131
	 */
132
	public function display(): void
133
	{
134
		// Sorting and pagination setup
135
		$sort_by_text = $this->get_sort_by_text_options();
136
		$sort_by_sql = $this->get_sort_options();
137
		$sort_key = $this->request->variable('sk', 't');
138
		$sort_dir = $this->request->variable('sd', 'd');
139
		$start = $this->request->variable('start', 0);
140
		$limit = (int) $this->config['topics_per_page'];
141
142
		// Filtering setup
143
		$limit_days = $this->get_limit_day_options();
144
		$selected_days = $this->request->variable('st', 0);
145
		$keywords = $this->request->variable('keywords', '', true);
146
147
		// Generate sorting and filtering selects
148
		$s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = '';
149
		gen_sort_selects($limit_days, $sort_by_text, $selected_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param);
150
151
		// Prepare SQL conditions
152
		$sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir === 'd') ? 'DESC' : 'ASC');
153
154
		// Fetch log data
155
		$log_data = [];
156
		$log_count = 0;
157
		$log_time = $this->calculate_timestamp($selected_days);
158
		$this->view_txn_log($log_data, $log_count, $limit, $start, $log_time, $sql_sort, $keywords);
159
160
		// Generate pagination
161
		$this->generate_pagination($log_count, $limit, $start, $u_sort_param, $keywords);
162
163
		// Assign template variables
164
		$this->assign_template_vars($s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param, $keywords, $start);
165
166
		// Assign log entries to template
167
		$this->assign_log_entries_to_template($log_data);
168
	}
169
170
	/**
171
	 * Get sort by text options for transactions
172
	 *
173
	 * @return array An associative array of sort options and their corresponding language strings
174
	 */
175
	private function get_sort_by_text_options(): array
176
	{
177
		return [
178
			'txn'      => $this->language->lang('PPDE_DT_SORT_TXN_ID'),
179
			'u'        => $this->language->lang('PPDE_DT_SORT_DONORS'),
180
			'ipn'      => $this->language->lang('PPDE_DT_SORT_IPN_STATUS'),
181
			'ipn_test' => $this->language->lang('PPDE_DT_SORT_IPN_TYPE'),
182
			'ps'       => $this->language->lang('PPDE_DT_SORT_PAYMENT_STATUS'),
183
			't'        => $this->language->lang('SORT_DATE'),
184
		];
185
	}
186
187
	/**
188
	 * Get sort options for transactions
189
	 *
190
	 * @return array An associative array of sort keys and their corresponding SQL column names
191
	 */
192
	private function get_sort_options(): array
193
	{
194
		return [
195
			'txn'      => 'txn.txn_id',
196
			'u'        => 'u.username_clean',
197
			'ipn'      => 'txn.confirmed',
198
			'ipn_test' => 'txn.test_ipn',
199
			'ps'       => 'txn.payment_status',
200
			't'        => 'txn.payment_date',
201
		];
202
	}
203
204
	/**
205
	 * Get limit day options for filtering
206
	 *
207
	 * @return array An associative array of day limits and their corresponding language strings
208
	 */
209
	private function get_limit_day_options(): array
210
	{
211
		return [
212
			0   => $this->language->lang('ALL_ENTRIES'),
213
			1   => $this->language->lang('1_DAY'),
214
			7   => $this->language->lang('7_DAYS'),
215
			14  => $this->language->lang('2_WEEKS'),
216
			30  => $this->language->lang('1_MONTH'),
217
			90  => $this->language->lang('3_MONTHS'),
218
			180 => $this->language->lang('6_MONTHS'),
219
			365 => $this->language->lang('1_YEAR'),
220
		];
221
	}
222
223
	/**
224
	 * Calculate the timestamp for filtering transactions based on the selected number of days
225
	 *
226
	 * @param int $selected_days Number of days to look back for transactions
227
	 * @return int The calculated timestamp, or 0 if no day limit is set
228
	 */
229
	private function calculate_timestamp(int $selected_days): int
230
	{
231
		return $selected_days > 0 ? time() - ($selected_days * self::SECONDS_IN_A_DAY) : 0;
232
	}
233
234
	/**
235
	 * View transaction log
236
	 *
237
	 * @param array  &$log       The result array with the logs
238
	 * @param int    &$log_count If $log_count is set to false, we will skip counting all entries in the database
239
	 *                           Otherwise an integer with the number of total matching entries is returned
240
	 * @param int     $limit     Limit the number of entries that are returned
241
	 * @param int     $offset    Offset when fetching the log entries, e.g. when paginating
242
	 * @param int     $log_time  Timestamp to filter logs
243
	 * @param string  $sort_by   SQL order option, e.g. 'l.log_time DESC'
244
	 * @param string  $keywords  Will only return log entries that have the keywords in log_operation or log_data
245
	 * @return void
246
	 */
247
	private function view_txn_log(array &$log, &$log_count, int $limit = 0, int $offset = 0, int $log_time = 0, string $sort_by = 'txn.payment_date DESC', string $keywords = ''): void
248
	{
249
		$count_logs = ($log_count !== false);
250
251
		$log = $this->get_logs($count_logs, $limit, $offset, $log_time, $sort_by, $keywords);
252
		$log_count = $this->get_log_count();
253
	}
254
255
	/**
256
	 * Get logs based on specified parameters
257
	 *
258
	 * @param bool   $count_logs Whether to count the total number of logs
259
	 * @param int    $limit      Maximum number of logs to retrieve
260
	 * @param int    $offset     Starting point for retrieving logs
261
	 * @param int    $log_time   Timestamp to filter logs
262
	 * @param string $sort_by    SQL ORDER BY clause
263
	 * @param string $keywords   Keywords to filter logs
264
	 * @return array Array of log entries
265
	 */
266
	private function get_logs(bool $count_logs = true, int $limit = 0, int $offset = 0, int $log_time = 0, string $sort_by = 'txn.payment_date DESC', string $keywords = ''): array
267
	{
268
		$this->entry_count = 0;
269
		$this->last_page_offset = $offset;
270
		$url_ary = [];
271
272
		if ($this->phpbb_admin_path && $this->ppde_actions->is_in_admin())
273
		{
274
			$url_ary['profile_url'] = append_sid($this->phpbb_admin_path . 'index.' . $this->php_ext, 'i=users&amp;mode=overview');
275
			$url_ary['txn_url'] = append_sid($this->phpbb_admin_path . 'index.' . $this->php_ext, 'i=-skouat-ppde-acp-ppde_module&amp;mode=transactions');
276
		}
277
		else
278
		{
279
			$url_ary['profile_url'] = append_sid($this->phpbb_root_path . 'memberlist.' . $this->php_ext, 'mode=viewprofile');
280
			$url_ary['txn_url'] = '';
281
		}
282
283
		$get_logs_sql_ary = $this->ppde_operator->get_logs_sql_ary($keywords, $sort_by, $log_time);
284
285
		if ($count_logs)
286
		{
287
			$this->entry_count = $this->ppde_operator->query_sql_count($get_logs_sql_ary, 'txn.transaction_id');
288
289
			if ($this->entry_count === 0)
290
			{
291
				// Save the queries, because there are no logs to display
292
				$this->last_page_offset = 0;
293
294
				return [];
295
			}
296
297
			// Return the user to the last page that is valid
298
			while ($this->last_page_offset >= $this->entry_count)
299
			{
300
				$this->last_page_offset = max(0, $this->last_page_offset - $limit);
301
			}
302
		}
303
304
		return $this->ppde_operator->build_log_entries($get_logs_sql_ary, $url_ary, $limit, $this->last_page_offset);
305
	}
306
307
	/**
308
	 * Get the total count of log entries
309
	 *
310
	 * @return int The total number of log entries
311
	 */
312
	public function get_log_count(): int
313
	{
314
		return (int) $this->entry_count ?: 0;
315
	}
316
317
	/**
318
	 * Generate pagination for transaction list
319
	 *
320
	 * @param int    $log_count    Total number of log entries
321
	 * @param int    $limit        Number of entries per page
322
	 * @param int    $start        Starting offset for the current page
323
	 * @param string $u_sort_param URL parameters for sorting
324
	 * @param string $keywords     Search keywords
325
	 */
326
	private function generate_pagination(int $log_count, int $limit, int $start, string $u_sort_param, string $keywords): void
327
	{
328
		$pagination = $this->container->get('pagination');
329
		$base_url = $this->u_action . '&amp;' . $u_sort_param . $this->get_keywords_param($keywords);
330
		$pagination->generate_template_pagination($base_url, 'pagination', 'start', $log_count, $limit, $start);
331
	}
332
333
	/**
334
	 * Get keywords parameter for URL
335
	 *
336
	 * @param string $keywords Search keywords
337
	 * @return string URL-encoded keywords parameter
338
	 */
339
	private function get_keywords_param(string $keywords): string
340
	{
341
		return !empty($keywords) ? '&amp;keywords=' . urlencode(htmlspecialchars_decode($keywords)) : '';
342
	}
343
344
	/**
345
	 * Assign common template variables
346
	 *
347
	 * @param string $s_limit_days
348
	 * @param string $s_sort_key
349
	 * @param string $s_sort_dir
350
	 * @param string $u_sort_param
351
	 * @param string $keywords
352
	 * @param int    $start
353
	 */
354
	private function assign_template_vars(string $s_limit_days, string $s_sort_key, string $s_sort_dir, string $u_sort_param, string $keywords, int $start): void
355
	{
356
		$this->template->assign_vars([
357
			'S_CLEARLOGS'  => $this->auth->acl_get('a_ppde_manage'),
358
			'S_KEYWORDS'   => $keywords,
359
			'S_LIMIT_DAYS' => $s_limit_days,
360
			'S_SORT_KEY'   => $s_sort_key,
361
			'S_SORT_DIR'   => $s_sort_dir,
362
			'U_ACTION'     => $this->u_action . '&amp;' . $u_sort_param . $this->get_keywords_param($keywords) . '&amp;start=' . $start,
363
		]);
364
	}
365
366
	/**
367
	 * Assign log entries to template
368
	 *
369
	 * @param array $log_data Array of log entries
370
	 */
371
	private function assign_log_entries_to_template(array $log_data): void
372
	{
373
		foreach ($log_data as $row)
374
		{
375
			$this->template->assign_block_vars('log', [
376
				'CONFIRMED'        => ($row['confirmed']) ? $this->language->lang('PPDE_DT_VERIFIED') : $this->language->lang('PPDE_DT_UNVERIFIED'),
377
				'DATE'             => $this->user->format_date($row['payment_date']),
378
				'ID'               => $row['transaction_id'],
379
				'PAYMENT_STATUS'   => $this->language->lang(['PPDE_DT_PAYMENT_STATUS_VALUES', strtolower($row['payment_status'])]),
380
				'TXN_ID'           => $row['txn_id'],
381
				'USERNAME'         => $row['username_full'],
382
				'S_CONFIRMED'      => (bool) $row['confirmed'],
383
				'S_PAYMENT_STATUS' => strtolower($row['payment_status']) === 'completed',
384
				'S_TXN_ERRORS'     => !empty($row['txn_errors']),
385
				'S_TEST_IPN'       => (bool) $row['test_ipn'],
386
			]);
387
		}
388
	}
389
390
	/**
391
	 * Set hidden fields for the transaction form
392
	 *
393
	 * @param string $id     Module id
394
	 * @param string $mode   Module category
395
	 * @param string $action Action name
396
	 */
397
	public function set_hidden_fields($id, $mode, $action): void
398
	{
399
		$this->args['action'] = $action;
400
		$this->args['hidden_fields'] = [
401
			'start'     => $this->request->variable('start', 0),
402
			'delall'    => $this->request->variable('delall', false, false, \phpbb\request\request_interface::POST),
403
			'delmarked' => $this->request->variable('delmarked', false, false, \phpbb\request\request_interface::POST),
404
			'i'         => $id,
405
			'mark'      => $this->request->variable('mark', [0]),
406
			'mode'      => $mode,
407
			'st'        => $this->request->variable('st', 0),
408
			'sk'        => $this->request->variable('sk', 't'),
409
			'sd'        => $this->request->variable('sd', 'd'),
410
		];
411
412
		// Prepare args depending on actions
413
		if (($this->args['hidden_fields']['delall'] || ($this->args['hidden_fields']['delmarked'] && count($this->args['hidden_fields']['mark']))) && $this->auth->acl_get('a_ppde_manage'))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($this->args['hidden_fie...cl_get('a_ppde_manage'), Probably Intended Meaning: $this->args['hidden_fiel...l_get('a_ppde_manage'))
Loading history...
414
		{
415
			$this->args['action'] = 'delete';
416
		}
417
		else if ($this->request->is_set('approve'))
418
		{
419
			$this->args['action'] = 'approve';
420
			$this->args['hidden_fields'] = array_merge($this->args['hidden_fields'], [
421
				'approve'             => true,
422
				'id'                  => $this->request->variable('id', 0),
423
				'txn_errors_approved' => $this->request->variable('txn_errors_approved', 0),
424
			]);
425
		}
426
		else if ($this->request->is_set('add'))
427
		{
428
			$this->args['action'] = 'add';
429
		}
430
		else if ($this->request->is_set_post('change'))
431
		{
432
			$this->args['action'] = 'change';
433
		}
434
	}
435
436
	/**
437
	 * Get hidden fields for the transaction form
438
	 *
439
	 * @return array
440
	 */
441
	public function get_hidden_fields(): array
442
	{
443
		return array_merge(
444
			['i'                           => $this->args['hidden_fields']['i'],
445
			 'mode'                        => $this->args['hidden_fields']['mode'],
446
			 'action'                      => $this->args['action'],
447
			 $this->id_prefix_name . '_id' => $this->args[$this->id_prefix_name . '_id']],
448
			$this->args['hidden_fields']);
449
	}
450
451
	/**
452
	 * {@inheritdoc}
453
	 */
454
	public function change(): void
455
	{
456
		$username = $this->request->variable('username', '', true);
457
		$donor_id = $this->request->variable('donor_id', 0);
458
		$transaction_id = $this->request->variable('id', 0);
459
460
		try
461
		{
462
			$user_id = $this->validate_user_id($username, $donor_id);
463
			$this->update_transaction($transaction_id, $user_id);
464
			$this->log_action('DT_UPDATED');
465
		}
466
		catch (transaction_exception $e)
467
		{
468
			$this->output_errors($e->get_errors());
469
		}
470
	}
471
472
	/**
473
	 * Validate and return the user ID
474
	 *
475
	 * @param string $username
476
	 * @param int    $donor_id
477
	 * @return int
478
	 * @throws transaction_exception
479
	 */
480
	private function validate_user_id(string $username, int $donor_id = 0): int
481
	{
482
		if (empty($username) && ($donor_id === ANONYMOUS || $this->request->is_set('u')))
483
		{
484
			return ANONYMOUS;
485
		}
486
487
		$user_id = ($username !== '') ? $this->user_loader->load_user_by_username($username) : $donor_id;
488
489
		if ($user_id <= ANONYMOUS)
490
		{
491
			throw new transaction_exception([$this->language->lang('PPDE_MT_DONOR_NOT_FOUND')]);
492
		}
493
494
		return $user_id;
495
	}
496
497
	/**
498
	 * Update transaction with new user ID
499
	 *
500
	 * @param int $transaction_id
501
	 * @param int $user_id
502
	 * @throws transaction_exception
503
	 */
504
	private function update_transaction($transaction_id, $user_id): void
505
	{
506
		$this->ppde_entity->load($transaction_id);
507
508
		if (!$this->ppde_entity->data_exists($this->ppde_entity->build_sql_data_exists()))
509
		{
510
			throw new transaction_exception([$this->language->lang('PPDE_DT_NO_TRANSACTION')]);
511
		}
512
513
		$this->ppde_entity->set_user_id($user_id)->add_edit_data();
514
	}
515
516
	/**
517
	 * Log the action in the admin log
518
	 *
519
	 * @param string $action_type
520
	 */
521
	private function log_action(string $action_type): void
522
	{
523
		$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_PPDE_' . $action_type);
524
		trigger_error($this->language->lang('PPDE_' . $action_type) . adm_back_link($this->u_action));
525
	}
526
527
	/**
528
	 * {@inheritdoc}
529
	 */
530
	public function add(): void
531
	{
532
		$transaction_data = $this->request_transaction_vars();
533
534
		if ($this->is_form_submitted())
535
		{
536
			try
537
			{
538
				$this->process_transaction($transaction_data);
539
				$this->log_action('MT_ADDED');
540
			}
541
			catch (transaction_exception $e)
542
			{
543
				$this->prepare_add_template($e->get_errors(), $transaction_data);
544
				return;
545
			}
546
		}
547
548
		$this->prepare_add_template([], $transaction_data);
549
	}
550
551
	/**
552
	 * Request transaction variables from the form
553
	 *
554
	 * @return array
555
	 */
556
	private function request_transaction_vars(): array
557
	{
558
		return [
559
			'MT_ANONYMOUS'          => $this->request->is_set('u'),
560
			'MT_USERNAME'           => $this->request->variable('username', '', true),
561
			'MT_FIRST_NAME'         => $this->request->variable('first_name', '', true),
562
			'MT_LAST_NAME'          => $this->request->variable('last_name', '', true),
563
			'MT_PAYER_EMAIL'        => $this->request->variable('payer_email', '', true),
564
			'MT_RESIDENCE_COUNTRY'  => $this->request->variable('residence_country', ''),
565
			'MT_MC_GROSS'           => $this->request->variable('mc_gross', 0.0),
566
			'MT_MC_CURRENCY'        => $this->request->variable('mc_currency', ''),
567
			'MT_MC_FEE'             => $this->request->variable('mc_fee', 0.0),
568
			'MT_PAYMENT_DATE_YEAR'  => $this->request->variable('payment_date_year', (int) $this->user->format_date(time(), 'Y')),
569
			'MT_PAYMENT_DATE_MONTH' => $this->request->variable('payment_date_month', (int) $this->user->format_date(time(), 'n')),
570
			'MT_PAYMENT_DATE_DAY'   => $this->request->variable('payment_date_day', (int) $this->user->format_date(time(), 'j')),
571
			'MT_PAYMENT_TIME'       => $this->request->variable('payment_time', $this->user->format_date(time(), 'H:i:s')),
572
			'MT_MEMO'               => $this->request->variable('memo', '', true),
573
		];
574
	}
575
576
	/**
577
	 * Process a transaction with the given transaction data and handle any errors that occur
578
	 *
579
	 * @param array $transaction_data The data for the transaction
580
	 * @throws transaction_exception
581
	 */
582
	private function process_transaction(array $transaction_data): void
583
	{
584
		$data_ary = $this->build_data_ary($transaction_data);
585
		$this->ppde_actions->log_to_db($data_ary);
586
587
		$this->ppde_actions->set_transaction_data($transaction_data);
588
		$this->ppde_actions->is_donor_is_member();
589
590
		$this->do_transactions_actions(
591
			$this->ppde_actions->get_donor_is_member() && !$transaction_data['MT_ANONYMOUS']
592
		);
593
	}
594
595
	/**
596
	 * Prepare data array before sending it to $this->entity
597
	 *
598
	 * @param array $transaction_data
599
	 * @return array
600
	 * @throws transaction_exception
601
	 */
602
	private function build_data_ary(array $transaction_data): array
603
	{
604
		$errors = [];
605
606
		try
607
		{
608
			$user_id = $this->validate_user_id($transaction_data['MT_USERNAME']);
609
		}
610
		catch (transaction_exception $e)
611
		{
612
			$errors = array_merge($errors, $e->get_errors());
613
		}
614
615
		try
616
		{
617
			$payment_date_time = $this->validate_payment_date_time($transaction_data);
618
		}
619
		catch (transaction_exception $e)
620
		{
621
			$errors = array_merge($errors, $e->get_errors());
622
		}
623
624
		try
625
		{
626
			$this->validate_transaction_amounts($transaction_data);
627
		}
628
		catch (transaction_exception $e)
629
		{
630
			$errors = array_merge($errors, $e->get_errors());
631
		}
632
633
		if (!empty($errors))
634
		{
635
			throw new transaction_exception($errors);
636
		}
637
638
		return [
639
			'business'          => $this->config['ppde_account_id'],
640
			'confirmed'         => true,
641
			'custom'            => implode('_', ['uid', $user_id, time()]),
642
			'exchange_rate'     => '',
643
			'first_name'        => $transaction_data['MT_FIRST_NAME'],
644
			'item_name'         => '',
645
			'item_number'       => implode('_', ['uid', $user_id, time()]),
646
			'last_name'         => $transaction_data['MT_LAST_NAME'],
647
			'mc_currency'       => $transaction_data['MT_MC_CURRENCY'],
648
			'mc_gross'          => $transaction_data['MT_MC_GROSS'],
649
			'mc_fee'            => $transaction_data['MT_MC_FEE'],
650
			'net_amount'        => 0.0, // This value is calculated in core_actions:log_to_db()
651
			'parent_txn_id'     => '',
652
			'payer_email'       => $transaction_data['MT_PAYER_EMAIL'],
653
			'payer_id'          => '',
654
			'payer_status'      => '',
655
			'payment_date'      => $payment_date_time,
656
			'payment_status'    => 'Completed',
657
			'payment_type'      => '',
658
			'memo'              => $transaction_data['MT_MEMO'],
659
			'receiver_id'       => '',
660
			'receiver_email'    => '',
661
			'residence_country' => strtoupper($transaction_data['MT_RESIDENCE_COUNTRY']),
662
			'settle_amount'     => 0.0,
663
			'settle_currency'   => '',
664
			'test_ipn'          => false,
665
			'txn_errors'        => '',
666
			'txn_id'            => 'PPDE' . gen_rand_string(13),
667
			'txn_type'          => 'ppde_manual_donation',
668
			'user_id'           => $user_id,
669
		];
670
	}
671
672
	/**
673
	 * Validate the payment date and time
674
	 *
675
	 * @param array $transaction_data
676
	 * @return int
677
	 * @throws transaction_exception
678
	 */
679
	private function validate_payment_date_time(array $transaction_data)
680
	{
681
		$payment_date = implode('-', [
682
			$transaction_data['MT_PAYMENT_DATE_YEAR'],
683
			$transaction_data['MT_PAYMENT_DATE_MONTH'],
684
			$transaction_data['MT_PAYMENT_DATE_DAY'],
685
		]);
686
687
		$payment_time = $transaction_data['MT_PAYMENT_TIME'];
688
		$date_time_string = $payment_date . ' ' . $payment_time;
689
690
		$payment_date_time = $this->parse_date_time($date_time_string);
691
692
		if ($payment_date_time === false)
693
		{
694
			throw new transaction_exception([$this->language->lang('PPDE_MT_PAYMENT_DATE_ERROR', $date_time_string)]);
695
		}
696
697
		if ($payment_date_time > time())
698
		{
699
			throw new transaction_exception([$this->language->lang('PPDE_MT_PAYMENT_DATE_FUTURE', $this->user->format_date($payment_date_time))]);
700
		}
701
702
		// Validate time
703
		$time_parts = explode(':', $payment_time);
704
		if (count($time_parts) < 2 || count($time_parts) > 3)
705
		{
706
			throw new transaction_exception([$this->language->lang('PPDE_MT_PAYMENT_TIME_ERROR', $payment_time)]);
707
		}
708
709
		$hours = (int) $time_parts[0];
710
		$minutes = (int) $time_parts[1];
711
		$seconds = isset($time_parts[2]) ? (int) $time_parts[2] : 0;
712
713
		if ($hours >= 24 || $minutes >= 60 || $seconds >= 60)
714
		{
715
			throw new transaction_exception([$this->language->lang('PPDE_MT_PAYMENT_TIME_ERROR', $payment_time)]);
716
		}
717
718
		return $payment_date_time;
719
	}
720
721
	/**
722
	 * Parse date and time string
723
	 *
724
	 * @param string $date_time_string
725
	 * @return int|false
726
	 */
727
	private function parse_date_time($date_time_string)
728
	{
729
		$formats = ['Y-m-d H:i:s', 'Y-m-d G:i', 'Y-m-d h:i:s a', 'Y-m-d g:i A'];
730
731
		foreach ($formats as $format)
732
		{
733
			$parsed = \DateTime::createFromFormat($format, $date_time_string);
734
			if ($parsed !== false)
735
			{
736
				return $parsed->getTimestamp();
737
			}
738
		}
739
740
		return false;
741
	}
742
743
	/**
744
	 * Validate transaction amounts
745
	 *
746
	 * @param array $transaction_data
747
	 * @throws transaction_exception
748
	 */
749
	private function validate_transaction_amounts(array $transaction_data)
750
	{
751
		$errors = [];
752
753
		if ($transaction_data['MT_MC_GROSS'] <= 0)
754
		{
755
			$errors[] = $this->language->lang('PPDE_MT_MC_GROSS_TOO_LOW');
756
		}
757
758
		if ($transaction_data['MT_MC_FEE'] < 0)
759
		{
760
			$errors[] = $this->language->lang('PPDE_MT_MC_FEE_NEGATIVE');
761
		}
762
763
		if ($transaction_data['MT_MC_FEE'] >= $transaction_data['MT_MC_GROSS'])
764
		{
765
			$errors[] = $this->language->lang('PPDE_MT_MC_FEE_TOO_HIGH');
766
		}
767
768
		if (!empty($errors))
769
		{
770
			throw new transaction_exception($errors);
771
		}
772
	}
773
774
	/**
775
	 * Perform actions for validated transaction
776
	 *
777
	 * @param bool $is_member
778
	 */
779
	private function do_transactions_actions($is_member): void
780
	{
781
		$this->ppde_actions->update_overview_stats();
782
		$this->ppde_actions->update_raised_amount();
783
784
		if ($is_member)
785
		{
786
			$this->ppde_actions->update_donor_stats();
787
			$this->ppde_actions->donors_group_user_add();
788
			$this->ppde_actions->notification->notify_donor_donation_received();
789
		}
790
	}
791
792
	/**
793
	 * Prepare and assign template variables for adding a new transaction
794
	 *
795
	 * @param array $errors           Array of error messages
796
	 * @param array $transaction_data Transaction data to be displayed in the form
797
	 */
798
	private function prepare_add_template(array $errors, array $transaction_data): void
799
	{
800
		$this->ppde_actions_currency->build_currency_select_menu((int) $this->config['ppde_default_currency']);
801
		$this->s_error_assign_template_vars($errors);
802
		$this->template->assign_vars($transaction_data);
803
		$this->template->assign_vars([
804
			'U_ACTION'             => $this->u_action,
805
			'U_BACK'               => $this->u_action,
806
			'S_ADD'                => true,
807
			'ANONYMOUS_USER_ID'    => ANONYMOUS,
808
			'U_FIND_USERNAME'      => append_sid($this->phpbb_root_path . 'memberlist.' . $this->php_ext, 'mode=searchuser&amp;form=manual_transaction&amp;field=username&amp;select_single=true'),
809
			'PAYMENT_TIME_FORMATS' => $this->get_payment_time_examples(),
810
		]);
811
	}
812
813
	/**
814
	 * Returns a list of valid times that the user can provide in the manual transaction form
815
	 *
816
	 * @return array Array of strings representing the current time, each in a different format
817
	 */
818
	private function get_payment_time_examples(): array
819
	{
820
		$formats = [
821
			'H:i:s',
822
			'G:i',
823
			'h:i:s a',
824
			'g:i A',
825
		];
826
827
		$examples = [];
828
829
		foreach ($formats as $format)
830
		{
831
			$examples[] = $this->user->format_date(time(), $format);
832
		}
833
834
		return $examples;
835
	}
836
837
	/**
838
	 * Output errors
839
	 *
840
	 * @param array $errors
841
	 */
842
	private function output_errors(array $errors)
843
	{
844
		trigger_error(implode('<br>', $errors) . adm_back_link($this->u_action), E_USER_WARNING);
845
	}
846
847
	/**
848
	 * Approve a transaction
849
	 */
850
	public function approve(): void
851
	{
852
		$transaction_id = (int) $this->args['hidden_fields']['id'];
853
		$txn_approved = empty($this->args['hidden_fields']['txn_errors_approved']);
854
855
		// Update DB record
856
		$this->ppde_entity->load($transaction_id);
857
		$this->ppde_entity->set_txn_errors_approved($txn_approved);
858
		$this->ppde_entity->save(false);
859
860
		// Prepare transaction settings before doing actions
861
		$transaction_data = $this->ppde_entity->get_data($this->ppde_operator->build_sql_data($transaction_id));
862
		$this->ppde_actions->set_transaction_data($transaction_data[0]);
863
		$this->ppde_actions->set_ipn_test_properties($this->ppde_entity->get_test_ipn());
864
		$this->ppde_actions->is_donor_is_member();
865
866
		if ($txn_approved)
867
		{
868
			$this->do_transactions_actions(!$this->ppde_actions->get_ipn_test() && $this->ppde_actions->get_donor_is_member());
869
		}
870
871
		$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_' . $this->lang_key_prefix . '_UPDATED', time());
872
	}
873
874
	/**
875
	 * View transaction details
876
	 */
877
	public function view(): void
878
	{
879
		// Request Identifier of the transaction
880
		$transaction_id = (int) $this->request->variable('id', 0);
881
882
		// Add additional fields to the table schema needed by entity->import()
883
		$additional_table_schema = [
884
			'item_username'    => ['name' => 'username', 'type' => 'string'],
885
			'item_user_colour' => ['name' => 'user_colour', 'type' => 'string'],
886
		];
887
888
		// Grab transaction data
889
		$data_ary = $this->ppde_entity->get_data($this->ppde_operator->build_sql_data($transaction_id), $additional_table_schema);
890
891
		array_map([$this, 'action_assign_template_vars'], $data_ary);
892
893
		$this->template->assign_vars([
894
			'U_FIND_USERNAME' => append_sid($this->phpbb_root_path . 'memberlist.' . $this->php_ext, 'mode=searchuser&amp;form=view_transactions&amp;field=username&amp;select_single=true'),
895
			'U_ACTION'        => $this->u_action,
896
			'U_BACK'          => $this->u_action,
897
			'S_VIEW'          => true,
898
		]);
899
	}
900
901
	/**
902
	 * Delete transaction(s)
903
	 */
904
	public function delete(): void
905
	{
906
		$where_sql = '';
907
908
		if ($this->args['hidden_fields']['delmarked'] && count($this->args['hidden_fields']['mark']))
909
		{
910
			$where_sql = $this->ppde_operator->build_marked_where_sql($this->args['hidden_fields']['mark']);
911
		}
912
913
		if ($where_sql || $this->args['hidden_fields']['delall'])
914
		{
915
			$this->ppde_entity->delete(0, '', $where_sql, $this->args['hidden_fields']['delall']);
916
			$this->ppde_actions->set_ipn_test_properties(true);
917
			$this->ppde_actions->update_overview_stats();
918
			$this->ppde_actions->set_ipn_test_properties(false);
919
			$this->ppde_actions->update_overview_stats();
920
			$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_' . $this->lang_key_prefix . '_PURGED', time());
921
		}
922
	}
923
924
	/**
925
	 * Assign action template variables
926
	 *
927
	 * @param array $data Transaction data
928
	 */
929
	protected function action_assign_template_vars(array $data): void
930
	{
931
		$this->assign_hidden_fields($data);
932
		$this->assign_currency_data($data);
933
		$this->assign_user_data($data);
934
		$this->assign_transaction_details($data);
935
		$this->assign_payment_details($data);
936
		$this->assign_error_data($data);
937
	}
938
939
	/**
940
	 * Assign hidden fields
941
	 *
942
	 * @param array $data
943
	 */
944
	private function assign_hidden_fields(array $data): void
945
	{
946
		$s_hidden_fields = build_hidden_fields([
947
			'id'                  => $data['transaction_id'],
948
			'donor_id'            => $data['user_id'],
949
			'txn_errors_approved' => $data['txn_errors_approved'],
950
		]);
951
		$this->template->assign_var('S_HIDDEN_FIELDS', $s_hidden_fields);
952
	}
953
954
	/**
955
	 * Assign currency data to template variables
956
	 *
957
	 * @param array $data Transaction data
958
	 */
959
	private function assign_currency_data(array $data): void
960
	{
961
		$this->ppde_actions_currency->set_currency_data_from_iso_code($data['mc_currency']);
962
		$this->ppde_actions_currency->set_currency_data_from_iso_code($data['settle_currency']);
963
964
		$this->template->assign_vars([
965
			'EXCHANGE_RATE'                   => '1 ' . $data['mc_currency'] . ' = ' . $data['exchange_rate'] . ' ' . $data['settle_currency'],
966
			'MC_GROSS'                        => $this->ppde_actions_currency->format_currency($data['mc_gross']),
967
			'MC_FEE'                          => $this->ppde_actions_currency->format_currency($data['mc_fee']),
968
			'MC_NET'                          => $this->ppde_actions_currency->format_currency($data['net_amount']),
969
			'SETTLE_AMOUNT'                   => $this->ppde_actions_currency->format_currency($data['settle_amount']),
970
			'L_PPDE_DT_SETTLE_AMOUNT'         => $this->language->lang('PPDE_DT_SETTLE_AMOUNT', $data['settle_currency']),
971
			'L_PPDE_DT_EXCHANGE_RATE_EXPLAIN' => $this->language->lang('PPDE_DT_EXCHANGE_RATE_EXPLAIN', $this->user->format_date($data['payment_date'])),
972
			'S_CONVERT'                       => !((int) $data['settle_amount'] === 0 && empty($data['exchange_rate'])),
973
		]);
974
	}
975
976
	/**
977
	 * Assign user data to template variables
978
	 *
979
	 * @param array $data Transaction data
980
	 */
981
	private function assign_user_data(array $data): void
982
	{
983
		$this->template->assign_vars([
984
			'BOARD_USERNAME' => get_username_string('full', $data['user_id'], $data['username'], $data['user_colour'], $this->language->lang('GUEST'), append_sid($this->phpbb_admin_path . 'index.' . $this->php_ext, 'i=users&amp;mode=overview')),
985
			'NAME'           => $data['first_name'] . ' ' . $data['last_name'],
986
			'PAYER_EMAIL'    => $data['payer_email'],
987
			'PAYER_ID'       => $data['payer_id'],
988
			'PAYER_STATUS'   => $data['payer_status'] ? $this->language->lang('PPDE_DT_VERIFIED') : $this->language->lang('PPDE_DT_UNVERIFIED'),
989
		]);
990
	}
991
992
	/**
993
	 * Assign transaction details to template variables
994
	 *
995
	 * @param array $data Transaction data
996
	 */
997
	private function assign_transaction_details(array $data): void
998
	{
999
		$this->template->assign_vars([
1000
			'ITEM_NAME'      => $data['item_name'],
1001
			'ITEM_NUMBER'    => $data['item_number'],
1002
			'MEMO'           => $data['memo'],
1003
			'RECEIVER_EMAIL' => $data['receiver_email'],
1004
			'RECEIVER_ID'    => $data['receiver_id'],
1005
			'TXN_ID'         => $data['txn_id'],
1006
		]);
1007
	}
1008
1009
	/**
1010
	 * Assign payment details to template variables
1011
	 *
1012
	 * @param array $data Transaction data
1013
	 */
1014
	private function assign_payment_details(array $data): void
1015
	{
1016
		$this->template->assign_vars([
1017
			'PAYMENT_DATE'   => $this->user->format_date($data['payment_date']),
1018
			'PAYMENT_STATUS' => $this->language->lang(['PPDE_DT_PAYMENT_STATUS_VALUES', strtolower($data['payment_status'])]),
1019
		]);
1020
	}
1021
1022
	/**
1023
	 * Assign error data to template variables
1024
	 *
1025
	 * @param array $data Transaction data
1026
	 */
1027
	private function assign_error_data(array $data): void
1028
	{
1029
		$this->template->assign_vars([
1030
			'S_ERROR'          => !empty($data['txn_errors']),
1031
			'S_ERROR_APPROVED' => !empty($data['txn_errors_approved']),
1032
			'ERROR_MSG'        => (!empty($data['txn_errors'])) ? $data['txn_errors'] : '',
1033
		]);
1034
	}
1035
}
1036