Passed
Push — develop-3.3.x ( 696004...ff97a1 )
by Mario
02:43
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_where = $this->prepare_sql_where($selected_days, $keywords);
155
		$sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir === 'd') ? 'DESC' : 'ASC');
156
157
		// Fetch log data
158
		$log_data = [];
159
		$log_count = 0;
160
		$this->view_txn_log($log_data, $log_count, $limit, $start, $sql_where, $sql_sort, $keywords);
0 ignored issues
show
Bug introduced by
$sql_where of type string is incompatible with the type integer expected by parameter $limit_days of skouat\ppde\controller\a...troller::view_txn_log(). ( 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 */ $sql_where, $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
	 * Prepare SQL where clause based on filtering options.
234
	 *
235
	 * @param int    $selected_days Number of days to limit the search to.
236
	 * @param string $keywords      Keywords to search for.
237
	 *
238
	 * @return string The prepared SQL where clause.
239
	 */
240
	private function prepare_sql_where(int $selected_days, string $keywords): string
0 ignored issues
show
Unused Code introduced by
The parameter $keywords is not used and could be removed. ( Ignorable by Annotation )

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

240
	private function prepare_sql_where(int $selected_days, /** @scrutinizer ignore-unused */ string $keywords): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

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