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