PHP WebShell
Текущая директория: /var/www/bitcardoApp/user/data
Просмотр файла: transactions.php
<?php
include '../common/header.php';
$user_id = $_SESSION['user_id'];
// --- Set default date to last 7 days if not set ---
if (empty($_GET['start']) || empty($_GET['end'])) {
$filter_end = date('Y-m-d');
$filter_start = date('Y-m-d', strtotime('-6 days'));
} else {
$filter_start = $_GET['start'];
$filter_end = $_GET['end'];
}
$page = isset($_GET['page']) && is_numeric($_GET['page']) && $_GET['page'] > 0 ? (int)$_GET['page'] : 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$filter_type = $_GET['type'] ?? '';
$filter_status = $_GET['status'] ?? '';
$filter_coin = $_GET['coin'] ?? '';
// =====================
// Perspective-aware WHERE (now includes fee)
// =====================
$where = 'WHERE 1=1';
$params = [];
$types = '';
$where .= " AND (
(t.type IN ('send','withdrawal') AND w_sender.user_id = ?)
OR
(t.type IN ('receive','deposit','giftcard_payout') AND w_receiver.user_id = ?)
OR
(t.type = 'swap' AND (w_sender.user_id = ? OR w_receiver.user_id = ?))
OR
(t.type = 'fee' AND (w_sender.user_id = ? OR w_receiver.user_id = ?))
)";
$params[] = $user_id; $types .= 'i';
$params[] = $user_id; $types .= 'i';
$params[] = $user_id; $types .= 'i';
$params[] = $user_id; $types .= 'i';
$params[] = $user_id; $types .= 'i';
$params[] = $user_id; $types .= 'i';
// Additional filters
if ($filter_type) { $where .= ' AND t.type = ?'; $params[] = $filter_type; $types .= 's'; }
if ($filter_status) { $where .= ' AND t.status = ?'; $params[] = $filter_status; $types .= 's'; }
if ($filter_coin) { $where .= ' AND t.coin = ?'; $params[] = $filter_coin; $types .= 's'; }
if ($filter_start) { $where .= ' AND t.created_at >= ?'; $params[] = $filter_start . " 00:00:00"; $types .= 's'; }
if ($filter_end) { $where .= ' AND t.created_at <= ?'; $params[] = $filter_end . " 23:59:59"; $types .= 's'; }
// Count for pagination
$count_sql = "
SELECT COUNT(*)
FROM transactions t
LEFT JOIN user_wallets w_sender ON t.sender_address = w_sender.wallet_add
LEFT JOIN user_wallets w_receiver ON t.receiver_address = w_receiver.wallet_add
$where
";
$stmt = $conn->prepare($count_sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$stmt->bind_result($total);
$stmt->fetch();
$stmt->close();
$totalPages = max(1, ceil($total / $limit));
// Fetch paginated transactions (include sender/receiver user_ids so we can sign fees correctly)
$query = "
SELECT t.*,
w_sender.user_id AS sender_uid, w_receiver.user_id AS receiver_uid,
w_sender.label AS sender_label, w_sender.wallet_add AS sender_wallet,
w_receiver.label AS receiver_label, w_receiver.wallet_add AS receiver_wallet,
u_sender.first_name AS sender_first, u_sender.last_name AS sender_last,
u_receiver.first_name AS receiver_first, u_receiver.last_name AS receiver_last
FROM transactions t
LEFT JOIN user_wallets w_sender ON t.sender_address = w_sender.wallet_add
LEFT JOIN users u_sender ON w_sender.user_id = u_sender.user_id
LEFT JOIN user_wallets w_receiver ON t.receiver_address = w_receiver.wallet_add
LEFT JOIN users u_receiver ON w_receiver.user_id = u_receiver.user_id
$where
ORDER BY t.created_at DESC
LIMIT $limit OFFSET $offset
";
$stmt = $conn->prepare($query);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
// Group by date
$groups = [];
while ($row = $result->fetch_assoc()) {
$date = date('M j, Y', strtotime($row['created_at']));
$groups[$date][] = $row;
}
$stmt->close();
// For filters
$typesList = ['send', 'receive', 'swap', 'deposit', 'withdrawal', 'fee', 'giftcard_payout'];
$coinRes = $conn->query("SELECT DISTINCT coin FROM transactions");
$coinOptions = [];
while ($row = $coinRes->fetch_assoc()) $coinOptions[] = $row['coin'];
$statusRes = $conn->query("SELECT DISTINCT status FROM transactions");
$statusOptions = [];
while ($row = $statusRes->fetch_assoc()) $statusOptions[] = $row['status'];
function truncate_left($string, $length = 30, $ellipsis = '...') {
if (!$string) return '';
$string = strval($string);
if (strlen($string) > $length) {
return $ellipsis . substr($string, -$length);
} else {
return $string;
}
}
?>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
<div class="container mt-3">
<div class="row">
<? include '../common/nav.php'; ?>
<main class="col-md-9 col-lg-10 px-md-5 mb-5">
<? include '../common/page-header.php'; ?>
<div class="container my-5 px-md-5">
<!-- Advanced Filters -->
<form class="mb-3 row g-2 align-items-end" method="get" id="advancedFilters">
<!-- TYPE -->
<div class="col-12 col-sm-2 d-none d-sm-block">
<label>Type</label>
<select class="form-select form-select-sm" name="type">
<option value="">All</option>
<?php foreach ($typesList as $t): ?>
<option value="<?= $t ?>"<?= $filter_type === $t ? ' selected' : '' ?>><?= ucfirst($t) ?></option>
<?php endforeach; ?>
</select>
</div>
<!-- STATUS -->
<div class="d-none d-md-inline col-sm-2">
<label>Status</label>
<select class="form-select form-select-sm" name="status">
<option value="">All</option>
<?php foreach ($statusOptions as $s): ?>
<option value="<?= $s ?>"<?= $filter_status === $s ? ' selected' : '' ?>><?= ucfirst($s) ?></option>
<?php endforeach; ?>
</select>
</div>
<!-- COIN -->
<div class="col-6 col-sm-2 d-none d-sm-block">
<label>Coin</label>
<select class="form-select form-select-sm" name="coin">
<option value="">All</option>
<?php foreach ($coinOptions as $c): ?>
<option value="<?= $c ?>"<?= $filter_coin === $c ? ' selected' : '' ?>><?= strtoupper($c) ?></option>
<?php endforeach; ?>
</select>
</div>
<!-- DATE RANGE -->
<div class="col-6 col-sm-3">
<label>Date From</label>
<input type="date" class="form-control form-control-sm" name="start" value="<?= htmlspecialchars($filter_start) ?>">
</div>
<div class="col-6 col-sm-3">
<label>Date To</label>
<input type="date" class="form-control form-control-sm" name="end" value="<?= htmlspecialchars($filter_end) ?>">
</div>
<!-- BUTTONS -->
<div class="col-6 col-sm-auto">
<button class="btn btn-primary btn-sm">Filter</button>
<a href="transactions.php" class="btn btn-secondary btn-sm">Reset</a>
</div>
<div class="col-6 col-sm-auto">
<button type="button" class="btn btn-outline-info btn-sm" id="downloadPngBtn">Download as PNG</button>
</div>
</form>
<div id="transactionList">
<?php if (empty($groups)): ?>
<div class="alert alert-info mt-5 text-center">No transactions yet.</div>
<?php else: ?>
<?php foreach ($groups as $date => $txs): ?>
<div class="transaction-group mb-3">
<h6 class="text-muted"><?= htmlspecialchars($date) ?></h6>
<?php foreach ($txs as $t): ?>
<?php
$coin = htmlspecialchars($t['coin']);
$amount_val = ($coin == 'NGN')
? number_format($t['amount'], 2)
: number_format($t['amount'], 6);
$amount_full = $amount_val . ' ' . $coin;
$type = $t['type']; // send, receive, swap, deposit, withdrawal, fee
$amountShow = $amount_full;
$amountClass = 'text-secondary';
$icon = '<i class="bi bi-question-circle"></i>';
$counterparty = '';
switch ($type) {
case 'send':
$amountShow = '-' . $amount_full;
$amountClass = 'text-danger';
$icon = '<i class="text-danger bi bi-box-arrow-in-up-right"></i>';
$counterparty = trim(($t['receiver_first'] ?? '') . ' ' . ($t['receiver_last'] ?? ''))
?: truncate_left($t['receiver_address']);
break;
case 'receive':
$amountShow = '+' . $amount_full;
$amountClass = 'text-success';
$icon = '<i class="text-success bi bi-box-arrow-in-down-right"></i>';
$counterparty = trim(($t['sender_first'] ?? '') . ' ' . ($t['sender_last'] ?? ''))
?: truncate_left($t['sender_address']);
break;
case 'swap':
$amountClass = 'text-info';
$icon = '<i class="text-info bi bi-shuffle"></i>';
$address = $t['receiver_address'] ?: $t['sender_address'];
$counterparty = truncate_left($address, 15);
$amountShow = $amount_full; // no +/- for swap
break;
case 'deposit':
$amountShow = '+' . $amount_full;
$amountClass = 'text-success';
$icon = '<i class="text-success bi bi-box-arrow-in-down-right"></i>';
$counterparty = 'Deposit Wallet';
break;
case 'withdrawal':
$amountShow = '-' . $amount_full;
$amountClass = 'text-danger';
$icon = '<i class="text-danger bi bi-box-arrow-in-up-right"></i>';
$counterparty = 'Withdrawal Wallet';
break;
case 'fee':
$isDebit = ((int)$t['sender_uid'] === (int)$user_id); // fee charged vs rebate
if ($isDebit) {
$amountShow = '-' . $amount_full;
$amountClass = 'text-danger';
$icon = '<i class="text-warning bi bi-receipt"></i>';
$counterparty = 'Fee';
} else {
$amountShow = '+' . $amount_full;
$amountClass = 'text-success';
$icon = '<i class="text-success bi bi-receipt"></i>';
$counterparty = 'Fee Rebate';
}
break;
case 'giftcard_payout':
$amountShow = '+' . $amount_full;
$amountClass = 'text-success'; // not used in markup yet but future-proof
$icon = '<i class="text-success bi bi-gift"></i>';
$counterparty = 'Giftcard Payout';
break;
}
$typeLabel = match ($type) {
'giftcard_payout' => 'Giftcard Payout',
default => ucfirst($type),
};
// Label preview (receiver side gives best hint)
$label = '';
if (in_array($type, ['send','receive'])) {
if (!empty($t['receiver_label'])) {
$label = '<span class="text-muted small">[' . htmlspecialchars($t['receiver_label']) . ']</span>';
} elseif (!empty($t['receiver_wallet'])) {
$label = '<span class="text-muted small">[' . truncate_left($t['receiver_wallet']) . ']</span>';
}
}
$status = htmlspecialchars($t['status']);
?>
<a href="transaction_detail.php?id=<?= $t['trans_id'] ?>" style="text-decoration:none;color:inherit">
<div class="bg-white p-3 rounded shadow-sm mb-2 transaction-item">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center gap-3">
<div class="transaction-icon"><?= $icon ?></div>
<div>
<strong><?= htmlspecialchars($counterparty) ?></strong>
<?= $label ?><br>
<small class="text-muted"><?= htmlspecialchars($typeLabel) ?></small>
</div>
</div>
<div class="text-end<?= (
$type === 'receive'
|| $type === 'deposit'
|| $type === 'giftcard_payout'
|| (!$isDebit && $type === 'fee')
)
? ' text-success'
: (
($type === 'send'
|| $type === 'withdrawal'
|| ($isDebit && $type === 'fee'))
? ' text-danger'
: ''
)
?>">
<strong><?= $amountShow ?></strong>
</div>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?php if ($totalPages > 1): ?>
<nav>
<ul class="pagination justify-content-center">
<li class="page-item<?= ($page <= 1) ? ' disabled' : '' ?>">
<a class="page-link" href="?<?= http_build_query(array_merge($_GET, ['page' => $page-1])) ?>" tabindex="-1">Previous</a>
</li>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<li class="page-item<?= ($i === $page) ? ' active' : '' ?>">
<a class="page-link" href="?<?= http_build_query(array_merge($_GET, ['page' => $i])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
<li class="page-item<?= ($page >= $totalPages) ? ' disabled' : '' ?>">
<a class="page-link" href="?<?= http_build_query(array_merge($_GET, ['page' => $page+1])) ?>">Next</a>
</li>
</ul>
</nav>
<?php endif; ?>
</div>
</main>
</div>
</div>
<? include '../common/footer.php'; ?>
<!-- Download visible list as PNG -->
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script>
document.getElementById('downloadPngBtn').addEventListener('click', function() {
html2canvas(document.getElementById('transactionList')).then(canvas => {
let link = document.createElement('a');
link.download = 'transactions.png';
link.href = canvas.toDataURL();
link.click();
});
});
</script>
<!-- Bitcardo-style Daterangepicker -->
<script>
$(function() {
var maxSpan = 93; // max days allowed (3 months)
function format(val) { return moment(val).format('YYYY-MM-DD'); }
$('#dateRangeInput').daterangepicker({
autoUpdateInput: false,
locale: { format: 'MMM DD, YYYY', cancelLabel: 'Clear', applyLabel: 'Apply' },
maxSpan: { days: maxSpan },
alwaysShowCalendars: true,
ranges: {
'Today': [moment(), moment()],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
},
startDate: "<?= $filter_start ?>",
endDate: "<?= $filter_end ?>"
});
$('#dateRangeInput').on('apply.daterangepicker', function(ev, picker) {
var days = picker.endDate.diff(picker.startDate, 'days');
if (days > maxSpan) {
$('#dateRangeError').removeClass('d-none').text('Maximum 3 months allowed.');
$(this).val(''); $('#dateStart').val(''); $('#dateEnd').val(''); return;
} else { $('#dateRangeError').addClass('d-none'); }
$(this).val(picker.startDate.format('MMM DD, YYYY') + ' - ' + picker.endDate.format('MMM DD, YYYY'));
$('#dateStart').val(format(picker.startDate)); $('#dateEnd').val(format(picker.endDate));
});
$('#dateRangeInput').on('cancel.daterangepicker', function() {
$(this).val(''); $('#dateStart').val(''); $('#dateEnd').val('');
});
if ($('#dateStart').val() && $('#dateEnd').val()) {
var start = moment($('#dateStart').val()).format('MMM DD, YYYY');
var end = moment($('#dateEnd').val()).format('MMM DD, YYYY');
$('#dateRangeInput').val(start + ' - ' + end);
}
});
</script>
<script>
document.getElementById('searchInput')?.addEventListener('input', function () {
const query = this.value.toLowerCase();
document.querySelectorAll('.transaction-group').forEach(group => {
const transactions = group.querySelectorAll('.transaction-item');
let visibleCount = 0;
transactions.forEach(item => {
const text = item.innerText.toLowerCase();
const isVisible = text.includes(query);
item.style.display = isVisible ? 'block' : 'none';
if (isVisible) visibleCount++;
});
group.style.display = visibleCount > 0 ? 'block' : 'none';
});
});
document.getElementById('downloadBtn')?.addEventListener('click', function () {
let csv = 'Name,Type,Amount\n';
document.querySelectorAll('.transaction-group').forEach(group => {
group.querySelectorAll('.transaction-item').forEach(item => {
if (item.style.display !== 'none') {
const name = item.querySelector('strong')?.innerText ?? '';
const type = item.querySelector('small')?.innerText ?? '';
const amount = item.querySelector('.text-end strong')?.innerText ?? '';
csv += `"${name}","${type}","${amount}"\n`;
}
});
});
const blob = new Blob([csv], {type: 'text/csv'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'transactions.csv';
link.click();
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
Выполнить команду
Для локальной разработки. Не используйте в интернете!