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>

Выполнить команду


Для локальной разработки. Не используйте в интернете!