Passed
Push — develop-3.3.x ( be8ddf...94e92c )
by Mario
02:45
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-2020 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 fiels
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
	 * @access public
83
	 */
84
	public function __construct(
85
		auth $auth,
86
		config $config,
87
		ContainerInterface $container,
88
		language $language,
89
		log $log,
90
		core $ppde_actions,
91
		currency $ppde_actions_currency,
92
		\skouat\ppde\entity\transactions $ppde_entity_transactions,
93
		transactions $ppde_operator_transactions,
94
		request $request,
95
		template $template,
96
		user $user,
97
		user_loader $user_loader,
98
		string $adm_relative_path,
99
		string $phpbb_root_path,
100
		string $php_ext,
101
		string $table_prefix,
102
		string $table_ppde_transactions
103
	)
104
	{
105
		$this->auth = $auth;
106
		$this->config = $config;
107
		$this->container = $container;
108
		$this->language = $language;
109
		$this->log = $log;
110
		$this->ppde_actions = $ppde_actions;
111
		$this->ppde_actions_currency = $ppde_actions_currency;
112
		$this->ppde_entity = $ppde_entity_transactions;
113
		$this->ppde_operator = $ppde_operator_transactions;
114
		$this->request = $request;
115
		$this->template = $template;
116
		$this->user = $user;
117
		$this->user_loader = $user_loader;
118
		$this->adm_relative_path = $adm_relative_path;
119
		$this->phpbb_admin_path = $phpbb_root_path . $adm_relative_path;
120
		$this->phpbb_root_path = $phpbb_root_path;
121
		$this->php_ext = $php_ext;
122
		$this->table_prefix = $table_prefix;
123
		$this->table_ppde_transactions = $table_ppde_transactions;
124
		parent::__construct(
125
			'transactions',
126
			'PPDE_DT',
127
			'transaction'
128
		);
129
	}
130
131
	/**
132
	 * {@inheritdoc}
133
	 */
134
	public function display(): void
135
	{
136
		// Sorting and pagination setup
137
		$sort_by_text = $this->get_sort_by_text_options();
138
		$sort_by_sql = $this->get_sort_options();
139
		$sort_key = $this->request->variable('sk', 't');
140
		$sort_dir = $this->request->variable('sd', 'd');
141
		$start = $this->request->variable('start', 0);
142
		$limit = (int) $this->config['topics_per_page'];
143
144
		// Filtering setup
145
		$limit_days = $this->get_limit_day_options();
146
		$selected_days = $this->request->variable('st', 0);
147
		$keywords = $this->request->variable('keywords', '', true);
148
149
		// Generate sorting and filtering selects
150
		$s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = '';
151
		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);
152
153
		// Prepare SQL conditions
154
		$sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir === 'd') ? 'DESC' : 'ASC');
155
156
		// Fetch log data
157
		$log_data = [];
158
		$log_count = 0;
159
		$log_time = $this->calculate_timestamp($selected_days);
160
		$this->view_txn_log($log_data, $log_count, $limit, $start, $log_time, $sql_sort, $keywords);
0 ignored issues
show
Bug introduced by
It seems like $log_time can also be of type null; however, parameter $log_time of skouat\ppde\controller\a...troller::view_txn_log() does only seem to accept integer, 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

160
		$this->view_txn_log($log_data, $log_count, $limit, $start, /** @scrutinizer ignore-type */ $log_time, $sql_sort, $keywords);
Loading history...
161
162
		// Generate pagination
163
		$this->generate_pagination($log_count, $limit, $start, $u_sort_param, $keywords);
164
165
		// Assign template variables
166
		$this->template->assign_vars([
167
			'S_CLEARLOGS'  => $this->auth->acl_get('a_ppde_manage'),
168
			'S_KEYWORDS'   => $keywords,
169
			'S_LIMIT_DAYS' => $s_limit_days,
170
			'S_SORT_KEY'   => $s_sort_key,
171
			'S_SORT_DIR'   => $s_sort_dir,
172
			'U_ACTION'     => $this->u_action . '&amp;' . $u_sort_param . $this->get_keywords_param($keywords) . '&amp;start=' . $start,
173
		]);
174
175
		// Assign log entries to template
176
		$this->assign_log_entries_to_template($log_data);
177
	}
178
179
	/**
180
	 * Get sort by text options for transactions.
181
	 *
182
	 * @return array An associative array of sort options and their corresponding language strings.
183
	 */
184
	private function get_sort_by_text_options(): array
185
	{
186
		return [
187
			'txn'      => $this->language->lang('PPDE_DT_SORT_TXN_ID'),
188
			'u'        => $this->language->lang('PPDE_DT_SORT_DONORS'),
189
			'ipn'      => $this->language->lang('PPDE_DT_SORT_IPN_STATUS'),
190
			'ipn_test' => $this->language->lang('PPDE_DT_SORT_IPN_TYPE'),
191
			'ps'       => $this->language->lang('PPDE_DT_SORT_PAYMENT_STATUS'),
192
			't'        => $this->language->lang('SORT_DATE'),
193
		];
194
	}
195
196
	/**
197
	 * Get sort options for transactions.
198
	 *
199
	 * @return array An associative array of sort keys and their corresponding SQL column names.
200
	 */
201
	private function get_sort_options(): array
202
	{
203
		return [
204
			'txn'      => 'txn.txn_id',
205
			'u'        => 'u.username_clean',
206
			'ipn'      => 'txn.confirmed',
207
			'ipn_test' => 'txn.test_ipn',
208
			'ps'       => 'txn.payment_status',
209
			't'        => 'txn.payment_date',
210
		];
211
	}
212
213
	/**
214
	 * Get limit day options for filtering.
215
	 *
216
	 * @return array An associative array of day limits and their corresponding language strings.
217
	 */
218
	private function get_limit_day_options(): array
219
	{
220
		return [
221
			0   => $this->language->lang('ALL_ENTRIES'),
222
			1   => $this->language->lang('1_DAY'),
223
			7   => $this->language->lang('7_DAYS'),
224
			14  => $this->language->lang('2_WEEKS'),
225
			30  => $this->language->lang('1_MONTH'),
226
			90  => $this->language->lang('3_MONTHS'),
227
			180 => $this->language->lang('6_MONTHS'),
228
			365 => $this->language->lang('1_YEAR'),
229
		];
230
	}
231
232
	/**
233
	 * Calculate the timestamp for filtering transactions based on the selected number of days.
234
	 *
235
	 * @param int $selected_days Number of days to look back for transactions.
236
	 *
237
	 * @return int|null The calculated timestamp, or null if no day limit is set.
238
	 */
239
	private function calculate_timestamp(int $selected_days)
240
	{
241
		if ($selected_days > 0)
242
		{
243
			return time() - ($selected_days * self::SECONDS_IN_A_DAY);
244
		}
245
246
		return null;
247
	}
248
249
	/**
250
	 * View transaction log.
251
	 *
252
	 * @param array &$log        The result array with the logs.
253
	 * @param mixed &$log_count  If $log_count is set to false, we will skip counting all entries in the database.
254
	 *                           Otherwise an integer with the number of total matching entries is returned.
255
	 * @param int    $limit      Limit the number of entries that are returned.
256
	 * @param int    $offset     Offset when fetching the log entries, e.g. when paginating.
257
	 * @param int    $log_time   Timestamp to filter logs.
258
	 * @param string $sort_by    SQL order option, e.g. 'l.log_time DESC'.
259
	 * @param string $keywords   Will only return log entries that have the keywords in log_operation or log_data.
260
	 *
261
	 * @return void Returns the offset of the last valid page, if the specified offset was invalid (too high)
262
	 * @access private
263
	 */
264
	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
265
	{
266
		$count_logs = ($log_count !== false);
267
268
		$log = $this->get_logs($count_logs, $limit, $offset, $log_time, $sort_by, $keywords);
269
		$log_count = $this->get_log_count();
270
	}
271
272
	/**
273
	 * Get logs based on specified parameters.
274
	 *
275
	 * @param bool   $count_logs Whether to count the total number of logs.
276
	 * @param int    $limit      Maximum number of logs to retrieve.
277
	 * @param int    $offset     Starting point for retrieving logs.
278
	 * @param int    $log_time   Timestamp to filter logs.
279
	 * @param string $sort_by    SQL ORDER BY clause.
280
	 * @param string $keywords   Keywords to filter logs.
281
	 *
282
	 * @return array Array of log entries.
283
	 * @access private
284
	 */
285
	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
286
	{
287
		$this->entry_count = 0;
288
		$this->last_page_offset = $offset;
289
		$url_ary = [];
290
291
		if ($this->phpbb_admin_path && $this->ppde_actions->is_in_admin())
292
		{
293
			$url_ary['profile_url'] = append_sid($this->phpbb_admin_path . 'index.' . $this->php_ext, 'i=users&amp;mode=overview');
294
			$url_ary['txn_url'] = append_sid($this->phpbb_admin_path . 'index.' . $this->php_ext, 'i=-skouat-ppde-acp-ppde_module&amp;mode=transactions');
295
296
		}
297
		else
298
		{
299
			$url_ary['profile_url'] = append_sid($this->phpbb_root_path . 'memberlist.' . $this->php_ext, 'mode=viewprofile');
300
			$url_ary['txn_url'] = '';
301
		}
302
303
		$get_logs_sql_ary = $this->ppde_operator->get_logs_sql_ary($keywords, $sort_by, $log_time);
304
305
		if ($count_logs)
306
		{
307
			$this->entry_count = $this->ppde_operator->query_sql_count($get_logs_sql_ary, 'txn.transaction_id');
308
309
			if ($this->entry_count === 0)
310
			{
311
				// Save the queries, because there are no logs to display
312
				$this->last_page_offset = 0;
313
314
				return [];
315
			}
316
317
			// Return the user to the last page that is valid
318
			while ($this->last_page_offset >= $this->entry_count)
319
			{
320
				$this->last_page_offset = max(0, $this->last_page_offset - $limit);
321
			}
322
		}
323
324
		return $this->ppde_operator->build_log_entries($get_logs_sql_ary, $url_ary, $limit, $this->last_page_offset);
325
	}
326
327
	/**
328
	 * Get the total count of log entries.
329
	 *
330
	 * @return int The total number of log entries.
331
	 */
332
	public function get_log_count(): int
333
	{
334
		return (int) $this->entry_count ?: 0;
335
	}
336
337
	/**
338
	 * Generate pagination for transaction list.
339
	 *
340
	 * @param int    $log_count    Total number of log entries.
341
	 * @param int    $limit        Number of entries per page.
342
	 * @param int    $start        Starting offset for the current page.
343
	 * @param string $u_sort_param URL parameters for sorting.
344
	 * @param string $keywords     Search keywords.
345
	 */
346
	private function generate_pagination(int $log_count, int $limit, int $start, string $u_sort_param, string $keywords): void
347
	{
348
		$pagination = $this->container->get('pagination');
349
		$base_url = $this->u_action . '&amp;' . $u_sort_param . $this->get_keywords_param($keywords);
350
		$pagination->generate_template_pagination($base_url, 'pagination', 'start', $log_count, $limit, $start);
351
	}
352
353
	/**
354
	 * Get keywords parameter for URL.
355
	 *
356
	 * @param string $keywords Search keywords.
357
	 *
358
	 * @return string URL-encoded keywords parameter.
359
	 */
360
	private function get_keywords_param(string $keywords): string
361
	{
362
		return !empty($keywords) ? '&amp;keywords=' . urlencode(htmlspecialchars_decode($keywords)) : '';
363
	}
364
365
	/**
366
	 * Assign log entries to template.
367
	 *
368
	 * @param array $log_data Array of log entries.
369
	 */
370
	private function assign_log_entries_to_template(array $log_data): void
371
	{
372
		foreach ($log_data as $row)
373
		{
374
			$this->template->assign_block_vars('log', [
375
				'CONFIRMED'        => ($row['confirmed']) ? $this->language->lang('PPDE_DT_VERIFIED') : $this->language->lang('PPDE_DT_UNVERIFIED'),
376
				'DATE'             => $this->user->format_date($row['payment_date']),
377
				'ID'               => $row['transaction_id'],
378
				'PAYMENT_STATUS'   => $this->language->lang(['PPDE_DT_PAYMENT_STATUS_VALUES', strtolower($row['payment_status'])]),
379
				'TXN_ID'           => $row['txn_id'],
380
				'USERNAME'         => $row['username_full'],
381
				'S_CONFIRMED'      => (bool) $row['confirmed'],
382
				'S_PAYMENT_STATUS' => strtolower($row['payment_status']) === 'completed',
383
				'S_TXN_ERRORS'     => !empty($row['txn_errors']),
384
				'S_TEST_IPN'       => (bool) $row['test_ipn'],
385
			]);
386
		}
387
	}
388
389
	/**
390
	 * Gets vars from POST then build a array of them
391
	 *
392
	 * @param string $id     Module id
393
	 * @param string $mode   Module categorie
394
	 * @param string $action Action name
395
	 *
396
	 * @return void
397
	 * @access private
398
	 */
399
	public function set_hidden_fields($id, $mode, $action): void
400
	{
401
		$this->args['action'] = $action;
402
		$this->args['hidden_fields'] = [
403
			'start'     => $this->request->variable('start', 0),
404
			'delall'    => $this->request->variable('delall', false, false, \phpbb\request\request_interface::POST),
405
			'delmarked' => $this->request->variable('delmarked', false, false, \phpbb\request\request_interface::POST),
406
			'i'         => $id,
407
			'mark'      => $this->request->variable('mark', [0]),
408
			'mode'      => $mode,
409
			'st'        => $this->request->variable('st', 0),
410
			'sk'        => $this->request->variable('sk', 't'),
411
			'sd'        => $this->request->variable('sd', 'd'),
412
		];
413
414
		// Prepares args depending actions
415
		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...
416
		{
417
			$this->args['action'] = 'delete';
418
		}
419
		else if ($this->request->is_set('approve'))
420
		{
421
			$this->args['action'] = 'approve';
422
			$this->args['hidden_fields'] = array_merge($this->args['hidden_fields'], [
423
				'approve'             => true,
424
				'id'                  => $this->request->variable('id', 0),
425
				'txn_errors_approved' => $this->request->variable('txn_errors_approved', 0),
426
			]);
427
		}
428
		else if ($this->request->is_set('add'))
429
		{
430
			$this->args['action'] = 'add';
431
		}
432
		else if ($this->request->is_set_post('change'))
433
		{
434
			$this->args['action'] = 'change';
435
		}
436
	}
437
438
	public function get_hidden_fields(): array
439
	{
440
		return array_merge(
441
			['i'                           => $this->args['hidden_fields']['i'],
442
			 'mode'                        => $this->args['hidden_fields']['mode'],
443
			 'action'                      => $this->args['action'],
444
			 $this->id_prefix_name . '_id' => $this->args[$this->id_prefix_name . '_id']],
445
			$this->args['hidden_fields']);
446
	}
447
448
	/**
449
	 * {@inheritdoc}
450
	 */
451
	public function change(): void
452
	{
453
		$username = $this->request->variable('username', '', true);
454
		$donor_id = $this->request->variable('donor_id', 0);
455
456
		try
457
		{
458
			$user_id = $this->validate_user_id($username, $donor_id);
459
		}
460
		catch (transaction_exception $e)
461
		{
462
			trigger_error(implode('<br>', $e->get_errors()) . adm_back_link($this->u_action), E_USER_WARNING);
463
		}
464
465
		// Request Identifier of the transaction
466
		$transaction_id = $this->request->variable('id', 0);
467
468
		$this->ppde_entity->load($transaction_id);
469
470
		if (!$this->ppde_entity->data_exists($this->ppde_entity->build_sql_data_exists()))
471
		{
472
			trigger_error($this->language->lang('PPDE_DT_NO_TRANSACTION') . adm_back_link($this->u_action), E_USER_WARNING);
473
		}
474
475
		$log_action = $this->ppde_entity
476
			->set_user_id($user_id)
477
			->add_edit_data()
478
		;
479
480
		$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_' . $this->lang_key_prefix . '_' . strtoupper($log_action));
481
		trigger_error($this->language->lang($this->lang_key_prefix . '_' . strtoupper($log_action)) . adm_back_link($this->u_action));
482
	}
483
484
	/**
485
	 * Returns the intended user ID
486
	 *
487
	 * @param string $username
488
	 * @param int    $donor_id
489
	 *
490
	 * @return int returns user_id
491
	 * @throws transaction_exception if the user_id is less than or equal to the default value for ANONYMOUS.
492
	 * @access private
493
	 */
494
	private function validate_user_id($username, $donor_id = 0): int
495
	{
496
		if ($this->should_return_anonymous($username, $donor_id))
497
		{
498
			return ANONYMOUS;
499
		}
500
501
		$user_id = ($username !== '') ? $this->user_loader->load_user_by_username($username) : $donor_id;
502
503
		if ($user_id <= ANONYMOUS)
504
		{
505
			throw (new transaction_exception())->set_errors([$this->language->lang('PPDE_MT_DONOR_NOT_FOUND')]);
506
		}
507
508
		return $user_id;
509
	}
510
511
	/**
512
	 * Determines if the given username and donor ID should result in an anonymous response.
513
	 *
514
	 * @param string $username The username to check.
515
	 * @param int    $donor_id The donor ID to check.
516
	 * @return bool Returns true if the username is empty and either the donor ID is ANONYMOUS or the 'u' parameter is
517
	 *                         set in the URL. Otherwise, returns false.
518
	 */
519
	private function should_return_anonymous(string $username, int $donor_id): bool
520
	{
521
		// if the username is empty and (donor_id is ANONYMOUS or 'u' parameter is set in URL),
522
		return $username === '' && ($donor_id === ANONYMOUS || $this->request->is_set('u'));
523
	}
524
525
	/**
526
	 * {@inheritdoc}
527
	 */
528
	public function add(): void
529
	{
530
		$errors = [];
531
532
		$transaction_data = $this->request_transaction_vars();
533
534
		if ($this->is_form_submitted())
535
		{
536
			$errors = $this->process_transaction($transaction_data, $errors);
537
		}
538
		$this->prepare_add_template($errors, $transaction_data);
539
	}
540
541
	/**
542
	 * Returns requested data from manual transaction form
543
	 *
544
	 * @return array
545
	 * @access private
546
	 */
547
	private function request_transaction_vars(): array
548
	{
549
		return [
550
			'MT_ANONYMOUS'          => $this->request->is_set('u'),
551
			'MT_USERNAME'           => $this->request->variable('username', '', true),
552
			'MT_FIRST_NAME'         => $this->request->variable('first_name', '', true),
553
			'MT_LAST_NAME'          => $this->request->variable('last_name', '', true),
554
			'MT_PAYER_EMAIL'        => $this->request->variable('payer_email', '', true),
555
			'MT_RESIDENCE_COUNTRY'  => $this->request->variable('residence_country', ''),
556
			'MT_MC_GROSS'           => $this->request->variable('mc_gross', 0.0),
557
			'MT_MC_CURRENCY'        => $this->request->variable('mc_currency', ''),
558
			'MT_MC_FEE'             => $this->request->variable('mc_fee', 0.0),
559
			'MT_PAYMENT_DATE_YEAR'  => $this->request->variable('payment_date_year', (int) $this->user->format_date(time(), 'Y')),
560
			'MT_PAYMENT_DATE_MONTH' => $this->request->variable('payment_date_month', (int) $this->user->format_date(time(), 'n')),
561
			'MT_PAYMENT_DATE_DAY'   => $this->request->variable('payment_date_day', (int) $this->user->format_date(time(), 'j')),
562
			'MT_PAYMENT_TIME'       => $this->request->variable('payment_time', $this->user->format_date(time(), 'H:i:s')),
563
			'MT_MEMO'               => $this->request->variable('memo', '', true),
564
		];
565
	}
566
567
	/**
568
	 * Process a transaction with the given transaction data and handle any errors that occur.
569
	 *
570
	 * @param array $transaction_data The data for the transaction.
571
	 * @param array $errors           The array to store any errors that occur during processing.
572
	 *
573
	 * @return array The updated array of errors after processing the transaction.
574
	 */
575
	private function process_transaction(array $transaction_data, array $errors): array
576
	{
577
		try
578
		{
579
			$this->ppde_actions->log_to_db($this->build_data_ary($transaction_data));
580
581
			// Prepare transaction settings before doing actions
582
			$this->ppde_actions->set_transaction_data($transaction_data);
583
			$this->ppde_actions->is_donor_is_member();
584
585
			$this->do_transactions_actions($this->ppde_actions->get_donor_is_member() && !$transaction_data['MT_ANONYMOUS']);
586
587
			$this->log_transaction($transaction_data);
588
		}
589
		catch (transaction_exception $e)
590
		{
591
			$errors = $e->get_errors();
592
		}
593
594
		return $errors;
595
	}
596
597
	/**
598
	 * Prepare data array before send it to $this->entity
599
	 *
600
	 * @param array $transaction_data
601
	 *
602
	 * @return array
603
	 * @throws transaction_exception
604
	 * @access private
605
	 */
606
	private function build_data_ary($transaction_data): array
607
	{
608
		$errors = [];
609
610
		try
611
		{
612
			$user_id = $this->validate_user_id($transaction_data['MT_USERNAME']);
613
		}
614
		catch (transaction_exception $e)
615
		{
616
			$errors = $e->get_errors();
617
		}
618
619
		$payment_date = implode('-', [
620
			$transaction_data['MT_PAYMENT_DATE_YEAR'],
621
			$transaction_data['MT_PAYMENT_DATE_MONTH'],
622
			$transaction_data['MT_PAYMENT_DATE_DAY'],
623
		]);
624
625
		$payment_date_timestamp_at_midnight = $this->user->get_timestamp_from_format('Y-m-d H:i:s', $payment_date . ' 00:00:00');
626
		$payment_time = $transaction_data['MT_PAYMENT_TIME'];
627
		$payment_time_timestamp = strtotime($payment_time);
628
629
		// Normalize payment time to start from today at midnight
630
		$payment_time_timestamp_from_midnight = $payment_time_timestamp - strtotime('00:00:00');
631
632
		$payment_date_time = $payment_date_timestamp_at_midnight + $payment_time_timestamp_from_midnight;
633
634
		$errors = array_merge($errors,
635
			$this->mc_gross_too_low($transaction_data),
636
			$this->mc_fee_negative($transaction_data),
637
			$this->mc_fee_too_high($transaction_data),
638
			$this->payment_date_timestamp_at_midnight($payment_date_timestamp_at_midnight, $payment_date),
639
			$this->payment_time_timestamp($payment_time_timestamp, $payment_date),
640
			$this->payment_date_time((string) $payment_date_time));
641
642
		if (count($errors))
643
		{
644
			throw (new transaction_exception())->set_errors($errors);
645
		}
646
647
		return [
648
			'business'          => $this->config['ppde_account_id'],
649
			'confirmed'         => true,
650
			'custom'            => implode('_', ['uid', $user_id, time()]),
651
			'exchange_rate'     => '',
652
			'first_name'        => $transaction_data['MT_FIRST_NAME'],
653
			'item_name'         => '',
654
			'item_number'       => implode('_', ['uid', $user_id, time()]),
655
			'last_name'         => $transaction_data['MT_LAST_NAME'],
656
			'mc_currency'       => $transaction_data['MT_MC_CURRENCY'],
657
			'mc_gross'          => $transaction_data['MT_MC_GROSS'],
658
			'mc_fee'            => $transaction_data['MT_MC_FEE'],
659
			'net_amount'        => 0.0, // This value is calculated in core_actions:log_to_db()
660
			'parent_txn_id'     => '',
661
			'payer_email'       => $transaction_data['MT_PAYER_EMAIL'],
662
			'payer_id'          => '',
663
			'payer_status'      => '',
664
			'payment_date'      => $payment_date_time,
665
			'payment_status'    => 'Completed',
666
			'payment_type'      => '',
667
			'memo'              => $transaction_data['MT_MEMO'],
668
			'receiver_id'       => '',
669
			'receiver_email'    => '',
670
			'residence_country' => strtoupper($transaction_data['MT_RESIDENCE_COUNTRY']),
671
			'settle_amount'     => 0.0,
672
			'settle_currency'   => '',
673
			'test_ipn'          => false,
674
			'txn_errors'        => '',
675
			'txn_id'            => 'PPDE' . gen_rand_string(13),
676
			'txn_type'          => 'ppde_manual_donation',
677
			'user_id'           => $user_id,
678
		];
679
	}
680
681
	/**
682
	 * Tests if mc_gross is to low
683
	 *
684
	 * @param array $data
685
	 *
686
	 * @return array
687
	 * @access private
688
	 */
689
	private function mc_gross_too_low($data): array
690
	{
691
		if ($data['MT_MC_GROSS'] <= 0)
692
		{
693
			return [$this->language->lang('PPDE_MT_MC_GROSS_TOO_LOW')];
694
		}
695
696
		return [];
697
	}
698
699
	/**
700
	 * Tests if mc_fee has a negative value
701
	 *
702
	 * @param array $data
703
	 *
704
	 * @return array
705
	 * @access private
706
	 */
707
	private function mc_fee_negative($data): array
708
	{
709
		if ($data['MT_MC_FEE'] < 0)
710
		{
711
			return [$this->language->lang('PPDE_MT_MC_FEE_NEGATIVE')];
712
		}
713
714
		return [];
715
	}
716
717
	/**
718
	 * Tests if mc_fee is too high
719
	 *
720
	 * @param array $data
721
	 *
722
	 * @return array
723
	 * @access private
724
	 */
725
	private function mc_fee_too_high($data): array
726
	{
727
		if ($data['MT_MC_FEE'] >= $data['MT_MC_GROSS'])
728
		{
729
			return [$this->language->lang('PPDE_MT_MC_FEE_TOO_HIGH')];
730
		}
731
732
		return [];
733
	}
734
735
	/**
736
	 * Tests if the date is valid
737
	 *
738
	 * @param string|false $payment_date_timestamp_at_midnight
739
	 * @param string       $payment_date
740
	 *
741
	 * @return array
742
	 * @access private
743
	 */
744
	private function payment_date_timestamp_at_midnight($payment_date_timestamp_at_midnight, $payment_date): array
745
	{
746
		if ($payment_date_timestamp_at_midnight === false)
747
		{
748
			return [$this->language->lang('PPDE_MT_PAYMENT_DATE_ERROR', $payment_date)];
749
		}
750
751
		return [];
752
	}
753
754
	/**
755
	 * @param int|false $payment_time_timestamp
756
	 * @param string    $payment_date
757
	 *
758
	 * @return array
759
	 * @access private
760
	 */
761
	private function payment_time_timestamp($payment_time_timestamp, $payment_date): array
762
	{
763
		if ($payment_time_timestamp === false)
764
		{
765
			return [$this->language->lang('PPDE_MT_PAYMENT_TIME_ERROR', $payment_date)];
766
		}
767
768
		return [];
769
	}
770
771
	/**
772
	 * @param string $payment_date_time
773
	 *
774
	 * @return array
775
	 * @access private
776
	 */
777
	private function payment_date_time($payment_date_time): array
778
	{
779
		if ($payment_date_time > time())
780
		{
781
			return [$this->language->lang('PPDE_MT_PAYMENT_DATE_FUTURE', $this->user->format_date($payment_date_time))];
782
		}
783
784
		return [];
785
	}
786
787
	/**
788
	 * Does actions for validated transaction
789
	 *
790
	 * @param bool $is_member
791
	 *
792
	 * @return void
793
	 * @access private
794
	 */
795
	private function do_transactions_actions($is_member): void
796
	{
797
		$this->ppde_actions->update_overview_stats();
798
		$this->ppde_actions->update_raised_amount();
799
800
		if ($is_member)
801
		{
802
			$this->ppde_actions->update_donor_stats();
803
			$this->ppde_actions->donors_group_user_add();
804
			$this->ppde_actions->notification->notify_donor_donation_received();
805
		}
806
	}
807
808
	/**
809
	 * Logs an entry in the phpBB admin log.
810
	 *
811
	 * @param array $transaction_data The data of the transaction to be logged.
812
	 *
813
	 * @return void
814
	 * @access private
815
	 */
816
	private function log_transaction(array $transaction_data): void
817
	{
818
		$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_PPDE_MT_ADDED', time(), [$transaction_data['MT_USERNAME']]);
819
		trigger_error($this->language->lang('PPDE_MT_ADDED') . adm_back_link($this->u_action));
820
	}
821
822
	/**
823
	 * Prepare and assign template variables for adding a new transaction.
824
	 *
825
	 * @param array $errors           Array of error messages.
826
	 * @param array $transaction_data Transaction data to be displayed in the form.
827
	 */
828
	private function prepare_add_template(array $errors, array $transaction_data): void
829
	{
830
		$this->ppde_actions_currency->build_currency_select_menu((int) $this->config['ppde_default_currency']);
831
		$this->s_error_assign_template_vars($errors);
832
		$this->template->assign_vars($transaction_data);
833
		$this->template->assign_vars([
834
			'U_ACTION'             => $this->u_action,
835
			'U_BACK'               => $this->u_action,
836
			'S_ADD'                => true,
837
			'ANONYMOUS_USER_ID'    => ANONYMOUS,
838
			'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'),
839
			'PAYMENT_TIME_FORMATS' => $this->get_payment_time_examples(),
840
		]);
841
	}
842
843
	/**
844
	 * Returns a list of valid times that the user can provide in the manual transaction form
845
	 *
846
	 * @return array Array of strings representing the current time, each in a different format
847
	 * @access private
848
	 */
849
	private function get_payment_time_examples(): array
850
	{
851
		$formats = [
852
			'H:i:s',
853
			'G:i',
854
			'h:i:s a',
855
			'g:i A',
856
		];
857
858
		$examples = [];
859
860
		foreach ($formats as $format)
861
		{
862
			$examples[] = $this->user->format_date(time(), $format);
863
		}
864
865
		return $examples;
866
	}
867
868
	/**
869
	 * {@inheritdoc}
870
	 */
871
	public function approve(): void
872
	{
873
		$transaction_id = (int) $this->args['hidden_fields']['id'];
874
		$txn_approved = empty($this->args['hidden_fields']['txn_errors_approved']);
875
876
		// Update DB record
877
		$this->ppde_entity->load($transaction_id);
878
		$this->ppde_entity->set_txn_errors_approved($txn_approved);
879
		$this->ppde_entity->save(false);
880
881
		// Prepare transaction settings before doing actions
882
		$transaction_data = $this->ppde_entity->get_data($this->ppde_operator->build_sql_data($transaction_id));
883
		$this->ppde_actions->set_transaction_data($transaction_data[0]);
884
		$this->ppde_actions->set_ipn_test_properties($this->ppde_entity->get_test_ipn());
885
		$this->ppde_actions->is_donor_is_member();
886
887
		if ($txn_approved)
888
		{
889
			$this->do_transactions_actions(!$this->ppde_actions->get_ipn_test() && $this->ppde_actions->get_donor_is_member());
890
		}
891
892
		$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_' . $this->lang_key_prefix . '_UPDATED', time());
893
	}
894
895
	/**
896
	 * {@inheritdoc}
897
	 */
898
	public function view(): void
899
	{
900
		// Request Identifier of the transaction
901
		$transaction_id = (int) $this->request->variable('id', 0);
902
903
		// add additional fields to the table schema needed by entity->import()
904
		$additional_table_schema = [
905
			'item_username'    => ['name' => 'username', 'type' => 'string'],
906
			'item_user_colour' => ['name' => 'user_colour', 'type' => 'string'],
907
		];
908
909
		// Grab transaction data
910
		$data_ary = $this->ppde_entity->get_data($this->ppde_operator->build_sql_data($transaction_id), $additional_table_schema);
911
912
		array_map([$this, 'action_assign_template_vars'], $data_ary);
913
914
		$this->template->assign_vars([
915
			'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'),
916
			'U_ACTION'        => $this->u_action,
917
			'U_BACK'          => $this->u_action,
918
			'S_VIEW'          => true,
919
		]);
920
	}
921
922
	/**
923
	 * {@inheritdoc}
924
	 */
925
	public function delete(): void
926
	{
927
		$where_sql = '';
928
929
		if ($this->args['hidden_fields']['delmarked'] && count($this->args['hidden_fields']['mark']))
930
		{
931
			$where_sql = $this->ppde_operator->build_marked_where_sql($this->args['hidden_fields']['mark']);
932
		}
933
934
		if ($where_sql || $this->args['hidden_fields']['delall'])
935
		{
936
			$this->ppde_entity->delete(0, '', $where_sql, $this->args['hidden_fields']['delall']);
937
			$this->ppde_actions->set_ipn_test_properties(true);
938
			$this->ppde_actions->update_overview_stats();
939
			$this->ppde_actions->set_ipn_test_properties(false);
940
			$this->ppde_actions->update_overview_stats();
941
			$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_' . $this->lang_key_prefix . '_PURGED', time());
942
		}
943
	}
944
945
	/**
946
	 * Assign action template variables.
947
	 *
948
	 * @param array $data Transaction data.
949
	 *
950
	 * @return void
951
	 * @access protected
952
	 */
953
	protected function action_assign_template_vars(array $data): void
954
	{
955
		$this->assign_hidden_fields($data);
956
		$this->assign_currency_data($data);
957
		$this->assign_user_data($data);
958
		$this->assign_transaction_details($data);
959
		$this->assign_payment_details($data);
960
		$this->assign_error_data($data);
961
	}
962
963
	private function assign_hidden_fields(array $data): void
964
	{
965
		$s_hidden_fields = build_hidden_fields([
966
			'id'                  => $data['transaction_id'],
967
			'donor_id'            => $data['user_id'],
968
			'txn_errors_approved' => $data['txn_errors_approved'],
969
		]);
970
		$this->template->assign_var('S_HIDDEN_FIELDS', $s_hidden_fields);
971
	}
972
973
	/**
974
	 * Assign currency data to template variables.
975
	 *
976
	 * @param array $data Transaction data.
977
	 */
978
	private function assign_currency_data(array $data): void
979
	{
980
		$currency_mc_data = $this->ppde_actions_currency->get_currency_data($data['mc_currency']);
981
		$currency_settle_data = $this->ppde_actions_currency->get_currency_data($data['settle_currency']);
982
983
		$this->template->assign_vars([
984
			'EXCHANGE_RATE'                   => '1 ' . $data['mc_currency'] . ' = ' . $data['exchange_rate'] . ' ' . $data['settle_currency'],
985
			'MC_GROSS'                        => $this->format_currency($data['mc_gross'], $currency_mc_data[0]),
986
			'MC_FEE'                          => $this->format_currency($data['mc_fee'], $currency_mc_data[0]),
987
			'MC_NET'                          => $this->format_currency($data['net_amount'], $currency_mc_data[0]),
988
			'SETTLE_AMOUNT'                   => $this->format_currency($data['settle_amount'], $currency_settle_data[0]),
989
			'L_PPDE_DT_SETTLE_AMOUNT'         => $this->language->lang('PPDE_DT_SETTLE_AMOUNT', $data['settle_currency']),
990
			'L_PPDE_DT_EXCHANGE_RATE_EXPLAIN' => $this->language->lang('PPDE_DT_EXCHANGE_RATE_EXPLAIN', $this->user->format_date($data['payment_date'])),
991
			'S_CONVERT'                       => !((int) $data['settle_amount'] === 0 && empty($data['exchange_rate'])),
992
		]);
993
	}
994
995
	/**
996
	 * Format currency amount.
997
	 *
998
	 * @param float $amount        The amount to format.
999
	 * @param array $currency_data Currency data including ISO code, symbol, and position.
1000
	 * @return string Formatted currency string.
1001
	 */
1002
	private function format_currency(float $amount, array $currency_data): string
1003
	{
1004
		return $this->ppde_actions_currency->format_currency(
1005
			$amount,
1006
			$currency_data['currency_iso_code'],
1007
			$currency_data['currency_symbol'],
1008
			(bool) $currency_data['currency_on_left']
1009
		);
1010
	}
1011
1012
	/**
1013
	 * Assign user data to template variables.
1014
	 *
1015
	 * @param array $data Transaction data.
1016
	 */
1017
	private function assign_user_data(array $data): void
1018
	{
1019
		$this->template->assign_vars([
1020
			'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')),
1021
			'NAME'           => $data['first_name'] . ' ' . $data['last_name'],
1022
			'PAYER_EMAIL'    => $data['payer_email'],
1023
			'PAYER_ID'       => $data['payer_id'],
1024
			'PAYER_STATUS'   => $data['payer_status'] ? $this->language->lang('PPDE_DT_VERIFIED') : $this->language->lang('PPDE_DT_UNVERIFIED'),
1025
		]);
1026
	}
1027
1028
	/**
1029
	 * Assign transaction details to template variables.
1030
	 *
1031
	 * @param array $data Transaction data.
1032
	 */
1033
	private function assign_transaction_details(array $data): void
1034
	{
1035
		$this->template->assign_vars([
1036
			'ITEM_NAME'      => $data['item_name'],
1037
			'ITEM_NUMBER'    => $data['item_number'],
1038
			'MEMO'           => $data['memo'],
1039
			'RECEIVER_EMAIL' => $data['receiver_email'],
1040
			'RECEIVER_ID'    => $data['receiver_id'],
1041
			'TXN_ID'         => $data['txn_id'],
1042
		]);
1043
	}
1044
1045
	/**
1046
	 * Assign payment details to template variables.
1047
	 *
1048
	 * @param array $data Transaction data.
1049
	 */
1050
	private function assign_payment_details(array $data): void
1051
	{
1052
		$this->template->assign_vars([
1053
			'PAYMENT_DATE'   => $this->user->format_date($data['payment_date']),
1054
			'PAYMENT_STATUS' => $this->language->lang(['PPDE_DT_PAYMENT_STATUS_VALUES', strtolower($data['payment_status'])]),
1055
		]);
1056
	}
1057
1058
	/**
1059
	 * Assign error data to template variables.
1060
	 *
1061
	 * @param array $data Transaction data.
1062
	 */
1063
	private function assign_error_data(array $data): void
1064
	{
1065
		$this->template->assign_vars([
1066
			'S_ERROR'          => !empty($data['txn_errors']),
1067
			'S_ERROR_APPROVED' => !empty($data['txn_errors_approved']),
1068
			'ERROR_MSG'        => (!empty($data['txn_errors'])) ? $data['txn_errors'] : '',
1069
		]);
1070
	}
1071
}
1072