Passed
Push — develop-3.3.x ( 1bac3f...ca0c68 )
by Mario
02:39
created

transactions::format_transaction_link()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 4
rs 10
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\operators;
12
13
use phpbb\db\driver\driver_interface;
14
15
class transactions
16
{
17
	protected $db;
18
	protected $ppde_transactions_log_table;
19
20
	/**
21
	 * Constructor
22
	 *
23
	 * @param driver_interface $db                          Database connection
24
	 * @param string           $ppde_transactions_log_table Table name
25
	 */
26
	public function __construct(driver_interface $db, string $ppde_transactions_log_table)
27
	{
28
		$this->db = $db;
29
		$this->ppde_transactions_log_table = $ppde_transactions_log_table;
30
	}
31
32
	/**
33
	 * Builds SQL Query to return Transaction log data
34
	 *
35
	 * @param int $transaction_id ID of the transaction to fetch (0 for all transactions)
36
	 * @return string SQL query string
37
	 */
38
	public function build_sql_data(int $transaction_id = 0): string
39
	{
40
		$sql_ary = [
41
			'SELECT'    => 'txn.*, u.username, u.user_colour',
42
			'FROM'      => [$this->ppde_transactions_log_table => 'txn'],
43
			'LEFT_JOIN' => [
44
				[
45
					'FROM' => [USERS_TABLE => 'u'],
46
					'ON'   => 'u.user_id = txn.user_id',
47
				],
48
			],
49
			'WHERE'     => $transaction_id ? 'txn.transaction_id = ' . $transaction_id : '',
50
			'ORDER_BY'  => 'txn.transaction_id',
51
		];
52
53
		// Return all transactions entities
54
		return $this->db->sql_build_query('SELECT', $sql_ary);
55
	}
56
57
	/**
58
	 * Builds SQL Query array for donor list
59
	 *
60
	 * @param bool   $detailed Whether to include detailed information
61
	 * @param string $order_by SQL ORDER BY clause
62
	 * @return array SQL query array
63
	 */
64
	public function sql_donorlist_ary(bool $detailed = false, string $order_by = ''): array
65
	{
66
		// Build sql request
67
		$sql_donorslist_ary = [
68
			'SELECT'   => 'txn.user_id, txn.mc_currency',
69
			'FROM'     => [$this->ppde_transactions_log_table => 'txn'],
70
			'WHERE'    => 'txn.user_id <> ' . ANONYMOUS . "
71
							AND txn.payment_status = 'Completed'
72
							AND txn.test_ipn = 0",
73
			'GROUP_BY' => 'txn.user_id, txn.mc_currency',
74
			'ORDER_BY' => $order_by,
75
		];
76
77
		if ($detailed)
78
		{
79
			$sql_donorslist_ary['SELECT'] = 'txn.user_id, txn.mc_currency, MAX(txn.transaction_id) AS max_txn_id, SUM(txn.mc_gross) AS amount, MAX(u.username)';
80
			$sql_donorslist_ary['LEFT_JOIN'] = [
81
				[
82
					'FROM' => [USERS_TABLE => 'u'],
83
					'ON'   => 'u.user_id = txn.user_id',
84
				]];
85
		}
86
87
		return $sql_donorslist_ary;
88
	}
89
90
	/**
91
	 * Builds SQL Query array for the last donation of a donor
92
	 *
93
	 * @param int $transaction_id ID of the transaction
94
	 * @return array SQL query array
95
	 */
96
	public function sql_last_donation_ary(int $transaction_id): array
97
	{
98
		return [
99
			'SELECT' => 'txn.payment_date, txn.mc_gross, txn.mc_currency',
100
			'FROM'   => [$this->ppde_transactions_log_table => 'txn'],
101
			'WHERE'  => 'txn.transaction_id = ' . $transaction_id,
102
		];
103
	}
104
105
	/**
106
	 * Builds SQL Query to return the donors list
107
	 *
108
	 * @param array $sql_donorlist_ary SQL query array
109
	 * @return string SQL query string
110
	 */
111
	public function build_sql_donorlist_data(array $sql_donorlist_ary): string
112
	{
113
		return $this->db->sql_build_query('SELECT', $sql_donorlist_ary);
114
	}
115
116
	/**
117
	 * Executes a COUNT query and returns the result
118
	 *
119
	 * @param array  $count_sql_ary  SQL query array
120
	 * @param string $selected_field Field to count
121
	 * @return int Count result
122
	 */
123
	public function query_sql_count(array $count_sql_ary, string $selected_field): int
124
	{
125
		$count_sql_ary['SELECT'] = 'COUNT(' . $selected_field . ') AS total_entries';
126
127
		if (array_key_exists('GROUP_BY', $count_sql_ary))
128
		{
129
			$count_sql_ary['SELECT'] = 'COUNT(DISTINCT ' . $count_sql_ary['GROUP_BY'] . ') AS total_entries';
130
		}
131
		unset($count_sql_ary['ORDER_BY'], $count_sql_ary['GROUP_BY']);
132
133
		$sql = $this->db->sql_build_query('SELECT', $count_sql_ary);
134
		$result = $this->db->sql_query($sql);
135
		$count = (int) $this->db->sql_fetchfield('total_entries');
136
		$this->db->sql_freeresult($result);
137
138
		return $count;
139
	}
140
141
	/**
142
	 * Builds SQL Query array for displaying simple transactions details
143
	 *
144
	 * @param string $keywords Search keywords
145
	 * @param string $sort_by  SQL ORDER BY clause
146
	 * @param int    $log_time Timestamp to filter logs
147
	 * @return array SQL query array
148
	 */
149
	public function get_logs_sql_ary(string $keywords, string $sort_by, int $log_time): array
150
	{
151
		$sql_keywords = $this->generate_sql_keyword($keywords);
152
153
		$sql_ary = [
154
			'SELECT'   => 'txn.transaction_id, txn.txn_id, txn.test_ipn, txn.confirmed, txn.txn_errors, txn.payment_date, txn.payment_status, txn.user_id, u.username, u.user_colour',
155
			'FROM'     => [
156
				$this->ppde_transactions_log_table => 'txn',
157
				USERS_TABLE                        => 'u',
158
			],
159
			'WHERE'    => 'txn.user_id = u.user_id ' . $sql_keywords,
160
			'ORDER_BY' => $sort_by,
161
		];
162
163
		if ($log_time)
164
		{
165
			$sql_ary['WHERE'] = 'txn.payment_date >= ' . (int) $log_time . ' AND ' . $sql_ary['WHERE'];
166
		}
167
168
		return $sql_ary;
169
	}
170
171
	/**
172
	 * Generates SQL condition for the specified keywords
173
	 *
174
	 * @param string $keywords           The keywords the user specified to search for
175
	 * @param string $statement_operator SQL operator to use ('AND' by default)
176
	 * @return string SQL condition string
177
	 */
178
	private function generate_sql_keyword(string $keywords, string $statement_operator = 'AND'): string
179
	{
180
		// Use no preg_quote for $keywords because this would lead to sole
181
		// backslashes being added. We also use an OR connection here for
182
		// spaces and the | string. Currently, regex is not supported for
183
		// searching (but may come later).
184
		$keywords = preg_split('#[\s|]+#u', utf8_strtolower($keywords), 0, PREG_SPLIT_NO_EMPTY);
0 ignored issues
show
Bug introduced by
The function utf8_strtolower was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

184
		$keywords = preg_split('#[\s|]+#u', /** @scrutinizer ignore-call */ utf8_strtolower($keywords), 0, PREG_SPLIT_NO_EMPTY);
Loading history...
185
		if (empty($keywords))
186
		{
187
			return '';
188
		}
189
190
		// Build pattern and keywords...
191
		$keywords = array_map(function ($keyword) {
192
			return $this->db->sql_like_expression($this->db->get_any_char() . $keyword . $this->db->get_any_char());
193
		}, $keywords);
194
195
		$sql_keywords = ' ' . $statement_operator . ' (';
196
		$columns = ['txn.txn_id', 'u.username'];
197
198
		$sql_clauses = [];
199
		foreach ($columns as $column_name)
200
		{
201
			$sql_lower = $this->db->sql_lower_text($column_name);
202
			$sql_clauses[] = $sql_lower . ' ' . implode(' OR ' . $sql_lower . ' ', $keywords);
203
		}
204
205
		$sql_keywords .= implode(' OR ', $sql_clauses) . ')';
206
207
		return $sql_keywords;
208
	}
209
210
	/**
211
	 * Retrieves user information based on the donor ID or email
212
	 *
213
	 * @param string     $type Type of identifier ('user', 'username', or 'email')
214
	 * @param int|string $arg  Identifier value
215
	 * @return array User data
216
	 */
217
	public function query_donor_user_data(string $type = 'user', $arg = 1): array
218
	{
219
		$sql_where = $this->build_donor_where_clause($type, $arg);
220
		return $this->fetch_donor_data($sql_where);
221
	}
222
223
	/**
224
	 * Builds SQL WHERE clause for donor query
225
	 *
226
	 * @param string $type Type of identifier
227
	 * @param mixed  $arg  Identifier value
228
	 * @return string SQL WHERE clause
229
	 */
230
	private function build_donor_where_clause(string $type, $arg): string
231
	{
232
		switch ($type)
233
		{
234
			case 'user':
235
				return ' WHERE user_id = ' . (int) $arg;
236
			case 'username':
237
				return " WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($arg)) . "'";
238
			case 'email':
239
				return " WHERE user_email = '" . $this->db->sql_escape(strtolower($arg)) . "'";
240
			default:
241
				return '';
242
		}
243
	}
244
245
	/**
246
	 * Fetches donor data from the database
247
	 *
248
	 * @param string $sql_where SQL WHERE clause
249
	 * @return array Donor data
250
	 */
251
	private function fetch_donor_data(string $sql_where): array
252
	{
253
		$sql = 'SELECT user_id, username, user_ppde_donated_amount
254
			FROM ' . USERS_TABLE . $sql_where;
255
		$result = $this->db->sql_query($sql);
256
		$row = $this->db->sql_fetchrow($result);
257
		$this->db->sql_freeresult($result);
258
259
		return $row ?: [];
260
	}
261
262
	/**
263
	 * Builds log entries for PayPal transactions
264
	 *
265
	 * @param array $get_logs_sql_ary SQL query array
266
	 * @param array $url_ary          Array of URLs for building links
267
	 * @param int   $limit            Maximum number of entries to return
268
	 * @param int   $last_page_offset Offset for pagination
269
	 * @return array Log entries
270
	 */
271
	public function build_log_entries(array $get_logs_sql_ary, array $url_ary, int $limit = 0, int $last_page_offset = 0): array
272
	{
273
		$sql = $this->db->sql_build_query('SELECT', $get_logs_sql_ary);
274
		$result = $this->db->sql_query_limit($sql, $limit, $last_page_offset);
275
276
		$log_entries = [];
277
278
		while ($row = $this->db->sql_fetchrow($result))
279
		{
280
			$log_entries[] = $this->build_log_entry($row, $url_ary);
281
		}
282
		$this->db->sql_freeresult($result);
283
284
		return $log_entries;
285
	}
286
287
	/**
288
	 * Builds a single log entry
289
	 *
290
	 * @param array $row     Database row data
291
	 * @param array $url_ary Array of URLs for building links
292
	 * @return array Formatted log entry
293
	 */
294
	private function build_log_entry(array $row, array $url_ary): array
295
	{
296
		return [
297
			'confirmed'      => $row['confirmed'],
298
			'payment_date'   => $row['payment_date'],
299
			'payment_status' => $row['payment_status'],
300
			'test_ipn'       => $row['test_ipn'],
301
			'transaction_id' => $row['transaction_id'],
302
			'txn_errors'     => $row['txn_errors'],
303
			'txn_id'         => $this->build_transaction_url($row['transaction_id'], $row['txn_id'], $url_ary['txn_url'], $row['confirmed']),
304
			'username_full'  => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, $url_ary['profile_url']),
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $guest_username of get_username_string(). ( Ignorable by Annotation )

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

304
			'username_full'  => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], /** @scrutinizer ignore-type */ false, $url_ary['profile_url']),
Loading history...
305
		];
306
	}
307
308
	/**
309
	 * Builds transaction URL for templates
310
	 *
311
	 * @param int    $id         Transaction ID
312
	 * @param string $txn_id     PayPal transaction ID
313
	 * @param string $custom_url Custom URL (optional)
314
	 * @param bool   $colour     Whether to apply color to the URL
315
	 * @return string Formatted transaction URL or plain transaction ID
316
	 */
317
	private function build_transaction_url(int $id, string $txn_id, string $custom_url = '', bool $colour = false): string
318
	{
319
		if (empty($custom_url))
320
		{
321
			return $txn_id;
322
		}
323
324
		$txn_url = $custom_url . '&amp;action=view&amp;id=' . $id;
325
		return $this->format_transaction_link($txn_url, $txn_id, $colour);
326
	}
327
328
	/**
329
	 * Formats the transaction link
330
	 *
331
	 * @param string $txn_url Transaction URL
332
	 * @param string $txn_id  PayPal transaction ID
333
	 * @param bool   $colour  Whether to apply color to the URL
334
	 * @return string Formatted transaction link
335
	 */
336
	private function format_transaction_link(string $txn_url, string $txn_id, bool $colour): string
337
	{
338
		$style = $colour ? '' : ' style="color: #ff0000;"';
339
		return sprintf('<a href="%s"%s>%s</a>', $txn_url, $style, $txn_id);
340
	}
341
342
	/**
343
	 * Builds SQL WHERE clause for marked transactions
344
	 *
345
	 * @param array $marked Array of marked transaction IDs
346
	 * @return string SQL WHERE clause
347
	 */
348
	public function build_marked_where_sql(array $marked): string
349
	{
350
		if (empty($marked))
351
		{
352
			return '';
353
		}
354
355
		return ' WHERE ' . $this->db->sql_in_set('transaction_id', array_map('intval', $marked));
356
	}
357
358
	/**
359
	 * Executes a query to count results for updating stats
360
	 *
361
	 * @param string $type     Type of count query
362
	 * @param bool   $test_ipn Whether to include test IPNs
363
	 * @return int Count result
364
	 */
365
	public function sql_query_count_result(string $type, bool $test_ipn): int
366
	{
367
		$field_name = strpos($type, 'transactions_count') !== false ? 'txn_id' : 'payer_id';
368
		$sql_ary = $this->sql_select_stats_main($field_name);
369
		$test_ipn_str = (int) $test_ipn;
370
371
		$this->add_where_clause($sql_ary, $type, $test_ipn_str);
372
373
		$sql = $this->db->sql_build_query('SELECT', $sql_ary);
374
		$result = $this->db->sql_query($sql);
375
		$count = (int) $this->db->sql_fetchfield('count_result');
376
		$this->db->sql_freeresult($result);
377
378
		return $count;
379
	}
380
381
	/**
382
	 * Builds base SQL query array for stats calculation
383
	 *
384
	 * @param string $field_name Name of the field to count
385
	 * @return array SQL query array
386
	 */
387
	private function sql_select_stats_main(string $field_name): array
388
	{
389
		return [
390
			'SELECT' => 'COUNT(DISTINCT txn.' . $field_name . ') AS count_result',
391
			'FROM'   => [$this->ppde_transactions_log_table => 'txn'],
392
		];
393
	}
394
395
	/**
396
	 * Adds WHERE clause to the SQL query array for stats calculation
397
	 *
398
	 * @param array  &$sql_ary      SQL query array (passed by reference)
399
	 * @param string  $type         Type of count query
400
	 * @param int     $test_ipn_str Test IPN flag (as integer)
401
	 */
402
	private function add_where_clause(array &$sql_ary, string $type, int $test_ipn_str): void
403
	{
404
		if (strpos($type, 'transactions_count') !== false)
405
		{
406
			$sql_ary['WHERE'] = "confirmed = 1 AND payment_status = 'Completed' AND txn.test_ipn = " . $test_ipn_str;
407
		}
408
		else if (strpos($type, 'known_donors_count') !== false)
409
		{
410
			$sql_ary['LEFT_JOIN'] = [
411
				[
412
					'FROM' => [USERS_TABLE => 'u'],
413
					'ON'   => 'txn.user_id = u.user_id',
414
				],
415
			];
416
			$sql_ary['WHERE'] = '(u.user_type = ' . USER_NORMAL . ' OR u.user_type = ' . USER_FOUNDER . ') AND txn.test_ipn = ' . $test_ipn_str;
417
		}
418
		else if (strpos($type, 'anonymous_donors_count') !== false)
419
		{
420
			$sql_ary['WHERE'] = 'txn.user_id = ' . ANONYMOUS . ' AND txn.test_ipn = ' . $test_ipn_str;
421
		}
422
	}
423
424
	/**
425
	 * Updates the user's donated amount
426
	 *
427
	 * @param int   $user_id User ID
428
	 * @param float $value   New donated amount
429
	 */
430
	public function sql_update_user_stats(int $user_id, float $value): void
431
	{
432
		$sql = 'UPDATE ' . USERS_TABLE . '
433
			SET user_ppde_donated_amount = ' . $value . '
434
			WHERE user_id = ' . $user_id;
435
		$this->db->sql_query($sql);
436
	}
437
438
	/**
439
	 * Prepares transaction data array for entity
440
	 *
441
	 * @param array $data Raw transaction data
442
	 * @return array Formatted transaction data
443
	 */
444
	public function build_transaction_data_ary(array $data): array
445
	{
446
		return [
447
			'business'          => $data['business'],
448
			'confirmed'         => (bool) $data['confirmed'],
449
			'custom'            => $data['custom'],
450
			'exchange_rate'     => $data['exchange_rate'],
451
			'first_name'        => $data['first_name'],
452
			'item_name'         => $data['item_name'],
453
			'item_number'       => $data['item_number'],
454
			'last_name'         => $data['last_name'],
455
			'mc_currency'       => $data['mc_currency'],
456
			'mc_gross'          => (float) $data['mc_gross'],
457
			'mc_fee'            => (float) $data['mc_fee'],
458
			'net_amount'        => (float) $data['net_amount'],
459
			'parent_txn_id'     => $data['parent_txn_id'],
460
			'payer_email'       => $data['payer_email'],
461
			'payer_id'          => $data['payer_id'],
462
			'payer_status'      => $data['payer_status'],
463
			'payment_date'      => $data['payment_date'],
464
			'payment_status'    => $data['payment_status'],
465
			'payment_type'      => $data['payment_type'],
466
			'memo'              => $data['memo'],
467
			'receiver_id'       => $data['receiver_id'],
468
			'receiver_email'    => $data['receiver_email'],
469
			'residence_country' => $data['residence_country'],
470
			'settle_amount'     => (float) $data['settle_amount'],
471
			'settle_currency'   => $data['settle_currency'],
472
			'test_ipn'          => (bool) $data['test_ipn'],
473
			'txn_errors'        => $data['txn_errors'],
474
			'txn_id'            => $data['txn_id'],
475
			'txn_type'          => $data['txn_type'],
476
			'user_id'           => (int) $data['user_id'],
477
		];
478
	}
479
}
480