PHP WebShell

Текущая директория: /var/www/bitcardoApp/backyard/models/dashboard

Просмотр файла: index.php

<?php
// backyard/models/dashboard/index.php
// Pure functions that read from $conn (procedural MySQLi). No side effects.

function dash_q_scalar(mysqli $conn, string $sql, string $field = 'c', int|float $default = 0) {
    try {
        $res = mysqli_query($conn, $sql);
        if ($res) {
            $row = mysqli_fetch_assoc($res);
            mysqli_free_result($res);
            if ($row && array_key_exists($field, $row)) {
                return is_numeric($row[$field]) ? 0 + $row[$field] : $default;
            }
        }
    } catch (\mysqli_sql_exception $e) {}
    return $default;
}

function dash_col_exists(mysqli $conn, string $table, string $col): bool {
    $table = mysqli_real_escape_string($conn, $table);
    $col   = mysqli_real_escape_string($conn, $col);
    try {
        $sql = "SHOW COLUMNS FROM `{$table}` LIKE '{$col}'";
        $res = mysqli_query($conn, $sql);
        if ($res) {
            $ok = (mysqli_num_rows($res) > 0);
            mysqli_free_result($res);
            return $ok;
        }
    } catch (\mysqli_sql_exception $e) {}
    return false;
}

function dash_users_pk(mysqli $conn): string {
    // Prefer users.user_id if present, else users.id
    return dash_col_exists($conn, 'users', 'user_id') ? 'user_id' : 'id';
}

function dash_detect_cwallet_balance_col(mysqli $conn): string {
    // Try common balance column names on cwallet
    $candidates = ['wallet_balance', 'balance', 'available_balance', 'cwallet_balance'];
    foreach ($candidates as $c) {
        if (dash_col_exists($conn, 'cwallet', $c)) return $c;
    }
    // fallback (will read as 0 if not present in row)
    return 'wallet_balance';
}

/**
 * Overview requirements:
 * 1) Today's Transactions amount = SUM(transactions.amount today) + SUM(card_trade.est_payout_ngn today)
 * 2) Users = distinct users who did transactions today (transactions + card_trade)
 * 3) Transactions = total count overall (transactions + card_trade)
 */
function dash_get_overview(mysqli $conn): array {
    $overview = [
        'users_count'        => 0,     // users who transacted today
        'transactions_count' => 0,     // TODAY's transactions count (transactions + card_trade)
        'today_tx_amount'    => 0.0,   // TODAY's amount (transactions.amount + card_trade.est_payout_ngn)
    ];

    // Today's total amount (transactions.amount + giftcard est_payout_ngn)
    $tx_today_amount = (float) dash_q_scalar(
        $conn,
        "SELECT COALESCE(SUM(`amount`),0) AS c
         FROM `transactions`
         WHERE DATE(`created_at`) = CURDATE()"
    );

    $gc_today_amount = (float) dash_q_scalar(
        $conn,
        "SELECT COALESCE(SUM(`est_payout_ngn`),0) AS c
         FROM `card_trade`
         WHERE DATE(`trade_created`) = CURDATE()"
    );

    $overview['today_tx_amount'] = $tx_today_amount + $gc_today_amount;

    // Today's transaction count (transactions + giftcards)
    $tx_today_count = (int) dash_q_scalar(
        $conn,
        "SELECT COUNT(*) AS c
         FROM `transactions`
         WHERE DATE(`created_at`) = CURDATE()"
    );

    $gc_today_count = (int) dash_q_scalar(
        $conn,
        "SELECT COUNT(*) AS c
         FROM `card_trade`
         WHERE DATE(`trade_created`) = CURDATE()"
    );

    $overview['transactions_count'] = $tx_today_count + $gc_today_count;

    // Users who transacted today (distinct union)
    $overview['users_count'] = (int) dash_q_scalar(
        $conn,
        "SELECT COUNT(*) AS c FROM (
            SELECT DISTINCT t.user_id AS uid
            FROM transactions t
            WHERE t.user_id IS NOT NULL AND t.user_id > 0
              AND DATE(t.created_at) = CURDATE()
            UNION
            SELECT DISTINCT ct.user_id AS uid
            FROM card_trade ct
            WHERE ct.user_id IS NOT NULL AND ct.user_id > 0
              AND DATE(ct.trade_created) = CURDATE()
        ) x",
        'c',
        0
    );

    return $overview;
}

/**
 * Platform wallets (FULL):
 * - shows ALL wallets in cwallet
 * - includes Platform Balance, All Users Balance, and growth %
 * - $limit <= 0 => no LIMIT
 */
function dash_get_platform_wallets(mysqli $conn, int $limit = 0): array {
    $wallets = [];
    $limit = (int)$limit;
    $balCol = dash_detect_cwallet_balance_col($conn);

    $sql = "SELECT * FROM `cwallet` ORDER BY `cwallet_id` ASC";
    if ($limit > 0) $sql .= " LIMIT {$limit}";

    try {
        $res = mysqli_query($conn, $sql);
        if (!$res) return $wallets;

        while ($w = mysqli_fetch_assoc($res)) {
            $coin   = trim((string)($w['coin'] ?? ''));
            $icon   = (string)($w['icon'] ?? '');
            $status = (string)($w['status'] ?? '');

            // platform balance (resilient)
            $platform_balance = 0.0;
            if (isset($w[$balCol]) && is_numeric($w[$balCol])) {
                $platform_balance = (float)$w[$balCol];
            } elseif (isset($w['wallet_balance']) && is_numeric($w['wallet_balance'])) {
                $platform_balance = (float)$w['wallet_balance'];
            }

            // user balance sum for this coin
            $user_balance = 0.0;
            if ($coin !== '') {
                $coin_safe = mysqli_real_escape_string($conn, $coin);
                $sum_sql = "SELECT COALESCE(SUM(`balance`),0) AS total_user_balance
                            FROM `user_wallets`
                            WHERE `coin` = '{$coin_safe}'";
                $user_balance = (float) dash_q_scalar($conn, $sum_sql, 'total_user_balance', 0.0);
            }

            // growth percent: (platform - users) / users * 100
            $growth_percent = 0.0;
            if ($user_balance > 0) {
                $growth_percent = (($platform_balance - $user_balance) / $user_balance) * 100.0;
            }

            $wallets[] = [
                'coin'             => $coin,
                'icon'             => $icon,
                'status'           => $status,
                'platform_balance' => $platform_balance,
                'user_balance'     => $user_balance,
                'growth_percent'   => $growth_percent,
            ];
        }
        mysqli_free_result($res);
    } catch (\mysqli_sql_exception $e) {}

    return $wallets;
}

function dash_get_recent_giftcard_batches(mysqli $conn, int $limit = 10): array {
    $rows = [];
    $limit = max(1, (int)$limit);
    $usersPk = dash_users_pk($conn);

    $sql = "
        SELECT
            ct.batch_ref,
            MAX(u.first_name) AS first_name,
            MAX(u.last_name)  AS last_name,
            SUM(ct.est_payout_ngn) AS total_payout,
            COUNT(*) AS cards_count,
            SUM(ct.trade_status IN ('SUCCESS','COMPLETED','APPROVED'))   AS ok_count,
            SUM(ct.trade_status IN ('PENDING','PROCESSING'))             AS pending_count,
            SUM(ct.trade_status IN ('DECLINED','FAILED','REJECTED'))     AS bad_count,
            MAX(ct.trade_created) AS last_time
        FROM card_trade ct
        LEFT JOIN users u ON u.`{$usersPk}` = ct.user_id
        WHERE ct.batch_ref IS NOT NULL AND ct.batch_ref <> ''
        GROUP BY ct.batch_ref
        ORDER BY last_time DESC
        LIMIT {$limit}
    ";

    try {
        if ($res = mysqli_query($conn, $sql)) {
            while ($r = mysqli_fetch_assoc($res)) {
                $rows[] = [
                    'batch_ref'     => $r['batch_ref'] ?? '',
                    'first_name'    => $r['first_name'] ?? '',
                    'last_name'     => $r['last_name'] ?? '',
                    'total_payout'  => (float)($r['total_payout'] ?? 0),
                    'cards_count'   => (int)($r['cards_count'] ?? 0),
                    'ok_count'      => (int)($r['ok_count'] ?? 0),
                    'pending_count' => (int)($r['pending_count'] ?? 0),
                    'bad_count'     => (int)($r['bad_count'] ?? 0),
                    'last_time'     => $r['last_time'] ?? null,
                ];
            }
            mysqli_free_result($res);
        }
    } catch (\mysqli_sql_exception $e) {}

    return $rows;
}

function dash_batch_status(array $b): string {
    $ok = (int)($b['ok_count'] ?? 0);
    $pd = (int)($b['pending_count'] ?? 0);
    $bd = (int)($b['bad_count'] ?? 0);
    $total = (int)($b['cards_count'] ?? 0);

    if ($total > 0 && $ok === $total) return 'Success';
    if ($pd > 0 && $bd === 0)        return 'Pending';
    if ($bd > 0 && $ok === 0 && $pd === 0) return 'Declined';
    if ($bd > 0 && ($ok > 0 || $pd > 0))   return 'Mixed';
    return 'Unknown';
}

function dash_get_latest_transactions(mysqli $conn, int $limit = 5): array {
    $rows = [];
    $limit = max(1, (int)$limit);
    $usersPk = dash_users_pk($conn);

    $sql = "
        SELECT
            t.trans_id,
            t.coin,
            t.user_id,
            t.wallet_id,
            t.sender_address,
            t.receiver_address,
            t.amount,
            t.type,
            t.txid,
            t.reference,
            t.provider,
            t.provider_meta,
            t.confirmation,
            t.status,
            t.applied,
            t.created_at,
            u.first_name,
            u.last_name,
            u.email
        FROM transactions t
        LEFT JOIN users u ON u.`{$usersPk}` = t.user_id
        ORDER BY t.created_at DESC, t.trans_id DESC
        LIMIT {$limit}
    ";

    try {
        if ($res = mysqli_query($conn, $sql)) {
            while ($r = mysqli_fetch_assoc($res)) $rows[] = $r;
            mysqli_free_result($res);
        }
    } catch (\mysqli_sql_exception $e) {}

    return $rows;
}

function dash_get_transaction_by_id(mysqli $conn, int $trans_id): ?array {
    $trans_id = (int)$trans_id;
    if ($trans_id <= 0) return null;

    $usersPk = dash_users_pk($conn);

    $sql = "
        SELECT
            t.*,
            u.first_name,
            u.last_name,
            u.email,
            u.phone
        FROM transactions t
        LEFT JOIN users u ON u.`{$usersPk}` = t.user_id
        WHERE t.trans_id = {$trans_id}
        LIMIT 1
    ";

    try {
        if ($res = mysqli_query($conn, $sql)) {
            $row = mysqli_fetch_assoc($res) ?: null;
            mysqli_free_result($res);
            return $row;
        }
    } catch (\mysqli_sql_exception $e) {}

    return null;
}

/**
 * Approve/complete a pending transaction:
 * - Locks tx row (FOR UPDATE)
 * - If pending-like and applied=0, credits user_wallets.balance and completes tx
 *
 * Returns: ['ok'=>bool, 'msg'=>string]
 */
function dash_approve_transaction(mysqli $conn, int $trans_id): array {
    $trans_id = (int)$trans_id;
    if ($trans_id <= 0) return ['ok'=>false,'msg'=>'Invalid transaction ID.'];

    mysqli_begin_transaction($conn);

    try {
        $txSql = "SELECT trans_id, user_id, wallet_id, coin, amount, status, applied
                  FROM transactions
                  WHERE trans_id = {$trans_id}
                  LIMIT 1
                  FOR UPDATE";
        $txRes = mysqli_query($conn, $txSql);
        if (!$txRes) { mysqli_rollback($conn); return ['ok'=>false,'msg'=>'Could not load transaction.']; }

        $tx = mysqli_fetch_assoc($txRes);
        mysqli_free_result($txRes);

        if (!$tx) { mysqli_rollback($conn); return ['ok'=>false,'msg'=>'Transaction not found.']; }

        $user_id    = (int)($tx['user_id'] ?? 0);
        $wallet_id  = trim((string)($tx['wallet_id'] ?? ''));
        $status     = strtolower(trim((string)($tx['status'] ?? 'pending')));
        $applied    = (int)($tx['applied'] ?? 0);
        $amount_str = trim((string)($tx['amount'] ?? '0'));

        if ($user_id <= 0 || !is_numeric($amount_str) || (float)$amount_str <= 0) {
            mysqli_rollback($conn);
            return ['ok'=>false,'msg'=>'Transaction data incomplete (user/amount).'];
        }

        $pendingLike = in_array($status, ['pending','processing','initiated','queued'], true);
        if (!$pendingLike) {
            mysqli_rollback($conn);
            return ['ok'=>false,'msg'=>'Only pending transactions can be approved.'];
        }

        if ($applied === 1) {
            if (dash_col_exists($conn, 'transactions', 'updated_at')) {
                mysqli_query($conn, "UPDATE transactions SET status='completed', updated_at=NOW() WHERE trans_id={$trans_id} LIMIT 1");
            } else {
                mysqli_query($conn, "UPDATE transactions SET status='completed' WHERE trans_id={$trans_id} LIMIT 1");
            }
            mysqli_commit($conn);
            return ['ok'=>true,'msg'=>'Already applied earlier. Status normalized to completed.'];
        }

        $targetWalletId = null;
        if ($wallet_id !== '' && ctype_digit($wallet_id)) {
            $targetWalletId = (int)$wallet_id;
        }

        if ($targetWalletId === null || $targetWalletId <= 0) {
            mysqli_rollback($conn);
            return ['ok'=>false,'msg'=>"Transaction wallet_id is missing/non-numeric. Ensure transactions.wallet_id stores user_wallets.wallet_id."];
        }

        $wSql = "SELECT wallet_id, user_id, coin, balance
                 FROM user_wallets
                 WHERE wallet_id = {$targetWalletId}
                 LIMIT 1
                 FOR UPDATE";
        $wRes = mysqli_query($conn, $wSql);
        if (!$wRes) { mysqli_rollback($conn); return ['ok'=>false,'msg'=>'Could not load user wallet.']; }

        $w = mysqli_fetch_assoc($wRes);
        mysqli_free_result($wRes);

        if (!$w) {
            mysqli_rollback($conn);
            return ['ok'=>false,'msg'=>'Target wallet not found for this wallet_id.'];
        }

        if ((int)$w['user_id'] !== $user_id) {
            mysqli_rollback($conn);
            return ['ok'=>false,'msg'=>'Security check failed: wallet does not belong to this user.'];
        }

        $hasWUpdatedAt = dash_col_exists($conn, 'user_wallets', 'updated_at');
        $creditSql = "UPDATE user_wallets
                      SET balance = balance + {$amount_str}" . ($hasWUpdatedAt ? ", updated_at = NOW()" : "") . "
                      WHERE wallet_id = {$targetWalletId}
                      LIMIT 1";
        if (!mysqli_query($conn, $creditSql)) {
            mysqli_rollback($conn);
            return ['ok'=>false,'msg'=>'Failed to credit wallet.'];
        }

        $hasTUpdatedAt = dash_col_exists($conn, 'transactions', 'updated_at');
        $updTx = "UPDATE transactions
                  SET status='completed',
                      applied=1" . ($hasTUpdatedAt ? ", updated_at=NOW()" : "") . "
                  WHERE trans_id={$trans_id}
                  LIMIT 1";
        if (!mysqli_query($conn, $updTx)) {
            mysqli_rollback($conn);
            return ['ok'=>false,'msg'=>'Failed to update transaction status.'];
        }

        mysqli_commit($conn);
        return ['ok'=>true,'msg'=>'Approved. Wallet credited and transaction completed.'];

    } catch (\mysqli_sql_exception $e) {
        mysqli_rollback($conn);
        return ['ok'=>false,'msg'=>'DB error: '.$e->getMessage()];
    }
}

function dash_get_batch_details(mysqli $conn, string $batch_ref): array {
    $batch_ref_safe = mysqli_real_escape_string($conn, $batch_ref);
    $out = ['batch' => null, 'cards' => []];
    $usersPk = dash_users_pk($conn);

    $sum_sql = "
        SELECT
            ct.batch_ref,
            MAX(u.first_name) AS first_name,
            MAX(u.last_name)  AS last_name,
            SUM(ct.est_payout_ngn) AS total_payout,
            COUNT(*) AS cards_count,
            SUM(ct.trade_status IN ('SUCCESS','COMPLETED','APPROVED'))   AS ok_count,
            SUM(ct.trade_status IN ('PENDING','PROCESSING'))             AS pending_count,
            SUM(ct.trade_status IN ('DECLINED','FAILED','REJECTED'))     AS bad_count,
            MAX(ct.trade_created) AS last_time
        FROM card_trade ct
        LEFT JOIN users u ON u.`{$usersPk}` = ct.user_id
        WHERE ct.batch_ref = '{$batch_ref_safe}'
        GROUP BY ct.batch_ref
        LIMIT 1
    ";
    if ($res = mysqli_query($conn, $sum_sql)) {
        $out['batch'] = mysqli_fetch_assoc($res) ?: null;
        mysqli_free_result($res);
    }

    $cards_sql = "
        SELECT
            ct.trade_id, ct.trade_ref, ct.trade_status, ct.trade_created,
            ct.card_value, ct.card_curr, ct.buy_price_snapshot, ct.est_payout_ngn,
            ct.note,
            cb.card_brand, gc.demon
        FROM card_trade ct
        LEFT JOIN card_brands cb ON cb.cbrand_id = ct.cbrand_id
        LEFT JOIN gift_cards  gc ON gc.gc_id = ct.gc_id
        WHERE ct.batch_ref = '{$batch_ref_safe}'
        ORDER BY ct.trade_created ASC, ct.trade_id ASC
    ";
    if ($cres = mysqli_query($conn, $cards_sql)) {
        while ($row = mysqli_fetch_assoc($cres)) {
            $out['cards'][] = $row;
        }
        mysqli_free_result($cres);
    }

    return $out;
}

function dash_get_images_for_trades(mysqli $conn, array $trade_ids): array {
    $imgs = [];
    if (empty($trade_ids)) return $imgs;

    $ids = array_map('intval', $trade_ids);
    $idlist = implode(',', $ids);

    $sql = "SELECT trade_id, image_id, path, original_name, mime, size, uploaded_at
            FROM card_trade_images
            WHERE trade_id IN ({$idlist})
            ORDER BY trade_id ASC, image_id ASC";
    if ($res = mysqli_query($conn, $sql)) {
        while ($r = mysqli_fetch_assoc($res)) {
            $tid = (int)$r['trade_id'];
            if (!isset($imgs[$tid])) $imgs[$tid] = [];
            $imgs[$tid][] = $r;
        }
        mysqli_free_result($res);
    }
    return $imgs;
}

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


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