PHP WebShell

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

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

<?php
require_once '../../config/db_config.php';
if (session_status() === PHP_SESSION_NONE) { session_start(); }

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

// ===== Auth =====
if (empty($_SESSION['user_id'])) {
    $_SESSION['error'] = 'Please sign in to submit a trade.';
    header("Location: ../../user/giftcards/submit_card.php");
    exit;
}
$user_id = (int)$_SESSION['user_id'];

// ===== CSRF =====
$csrf = $_POST['csrf_token'] ?? '';
if (empty($csrf) || !hash_equals($_SESSION['csrf_token'] ?? '', $csrf)) {
    $_SESSION['error'] = 'Security check failed. Please try again.';
    header("Location: ../../user/giftcards/submit_card.php");
    exit;
}

// ===== Input =====
$note        = trim($_POST['note'] ?? '');
$cbrand_ids  = $_POST['cbrand_id'] ?? [];
$gc_ids      = $_POST['gc_id'] ?? [];
$card_values = $_POST['card_value'] ?? [];

if (!is_array($cbrand_ids) || !is_array($gc_ids) || !is_array($card_values)) {
    $_SESSION['error'] = "Invalid submission payload.";
    header("Location: ../../user/giftcards/submit_card.php");
    exit;
}

$entry_count = min(count($cbrand_ids), count($gc_ids), count($card_values));
if ($entry_count === 0) {
    $_SESSION['error'] = "Incomplete submission. Please fill in all required fields.";
    header("Location: ../../user/giftcards/submit_card.php");
    exit;
}

// ===== Config (mirror frontend limits) =====
$MAX_FILES = 6;
$MAX_SIZE  = 8 * 1024 * 1024; // 8MB
$ALLOWED_MIME = ['image/jpeg','image/png','image/webp','image/heic','application/pdf'];
$ALLOWED_EXT  = ['jpg','jpeg','png','webp','heic','pdf'];

// ===== Helpers =====
function gen_batch_ref(): string {
    // Pattern: 2 uppercase letters + 4 digits (e.g., AB2390)
    $letters = strtoupper(bin2hex(random_bytes(1))); // 2 hex chars -> we’ll map to letters
    // Map hex to letters A-P to ensure A-Z feel
    $map = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P'];
    $a = $map[hexdec($letters[0])];
    $b = $map[hexdec($letters[1])];
    $digits = str_pad((string)random_int(0, 9999), 4, '0', STR_PAD_LEFT);
    return $a.$b.$digits;
}
function ensure_dir($path): bool { return is_dir($path) ?: mkdir($path, 0755, true); }
function safe_ext(string $name, array $allowed_ext): string {
    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    return in_array($ext, $allowed_ext, true) ? $ext : 'bin';
}
function format_ngn($n): string { return '₦' . number_format((float)$n, 2, '.', ','); }

// ===== Create batch + legacy trade_ref for backward compat =====
$batch_ref = gen_batch_ref();   // NEW: authoritative batch ID (AB2390)
$trade_ref = $batch_ref;        // keep old column aligned for now

// ===== Base upload dir: /year/month/batch_ref/ =====
$year  = date('Y');
$month = date('m');
$base_upload_dir = "../../backyard/uploads/cards/{$year}/{$month}/{$batch_ref}/";
if (!ensure_dir($base_upload_dir)) {
    $_SESSION['error'] = "Unable to prepare upload directory.";
    header("Location: ../../user/giftcards/submit_card.php");
    exit;
}

// ===== Prepare statements (once) =====
$gc_stmt = $conn->prepare("
    SELECT gc.buy_price, gc.card_curr
    FROM gift_cards gc
    JOIN card_brands cb ON cb.cbrand_id = gc.cbrand_id
    WHERE gc.gc_id = ? AND gc.cbrand_id = ? AND gc.status = 1 AND cb.status = 1
");

$trade_stmt = $conn->prepare("
    INSERT INTO card_trade
        (trade_ref, batch_ref, card_ref, user_id, cbrand_id, gc_id, card_value, card_curr,
         buy_price_snapshot, est_payout_ngn, note, trade_status, trade_created)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NOW())
");
/*
 * Bind types (11 placeholders before 'pending', NOW()):
 * s trade_ref
 * s batch_ref
 * s card_ref
 * i user_id
 * i cbrand_id
 * i gc_id
 * d card_value
 * s card_curr
 * d buy_price_snapshot
 * d est_payout_ngn
 * s note
 * => "sssiiidsdds"
 */

$image_stmt = $conn->prepare("
    INSERT INTO card_trade_images
        (trade_id, path, original_name, mime, size, uploaded_at)
    VALUES (?, ?, ?, ?, ?, NOW())
");

$finfo = new finfo(FILEINFO_MIME_TYPE);

// ===== Process entries =====
$cards_created = 0;
$cards_failed  = 0;
$total_estimate = 0.0;
$error_messages = [];

// Sequential per-card reference inside the batch (…-001, …-002, …)
$seq = 0;

for ($i = 0; $i < $entry_count; $i++) {
    try {
        $cbrand_id  = (int)$cbrand_ids[$i];
        $gc_id      = (int)$gc_ids[$i];
        $card_value = (float)$card_values[$i];

        // Basic validation
        if ($cbrand_id <= 0 || $gc_id <= 0 || $card_value <= 0) {
            $cards_failed++;
            $error_messages[] = "Card #".($i+1).": invalid brand/card/value.";
            continue;
        }

        // Files presence
        $img_field = "card_images_{$i}";
        if (empty($_FILES[$img_field]) || empty($_FILES[$img_field]['name'])) {
            $cards_failed++;
            $error_messages[] = "Card #".($i+1).": no image(s) uploaded.";
            continue;
        }

        // Validate gift card & get snapshot price/curr
        $gc_stmt->bind_param("ii", $gc_id, $cbrand_id);
        $gc_stmt->execute();
        $res = $gc_stmt->get_result();
        if ($res->num_rows === 0) {
            $cards_failed++;
            $error_messages[] = "Card #".($i+1).": gift card not available or inactive.";
            continue;
        }
        $gc_row = $res->fetch_assoc();
        $buy_price = (float)$gc_row['buy_price'];  // NGN per 1 unit
        $card_curr = (string)$gc_row['card_curr'];
        if ($buy_price <= 0) {
            $cards_failed++;
            $error_messages[] = "Card #".($i+1).": invalid buy price.";
            continue;
        }

        $est_payout = $buy_price * $card_value;

        // Build per-card reference
        $seq++;
        $card_ref = $batch_ref . '-' . str_pad((string)$seq, 3, '0', STR_PAD_LEFT); // e.g., AB2390-001

        // Per-entry transaction
        $conn->begin_transaction();

        // Insert trade master row
        $note_param = $note; // keep as string
        $trade_stmt->bind_param(
            "sssiiidsdds",
            $trade_ref, $batch_ref, $card_ref, $user_id, $cbrand_id, $gc_id,
            $card_value, $card_curr, $buy_price, $est_payout, $note_param
        );
        $trade_stmt->execute();
        $trade_id = $conn->insert_id;

        // Validate files (count/size/mime)
        $names = $_FILES[$img_field]['name'];
        $tmps  = $_FILES[$img_field]['tmp_name'];
        $errs  = $_FILES[$img_field]['error'];
        $sizes = $_FILES[$img_field]['size'];

        // Count check
        $file_count = 0;
        foreach ($tmps as $tmpv) { if (!empty($tmpv)) $file_count++; }
        if ($file_count === 0) {
            throw new RuntimeException("no valid files uploaded.");
        }
        if ($file_count > $MAX_FILES) {
            throw new RuntimeException("too many files (max {$MAX_FILES}).");
        }

        // First pass: validate all
        for ($j = 0; $j < count($tmps); $j++) {
            if (empty($tmps[$j])) continue;

            if ($errs[$j] !== UPLOAD_ERR_OK) {
                throw new RuntimeException("upload error on file #".($j+1).".");
            }
            if ($sizes[$j] > $MAX_SIZE) {
                throw new RuntimeException("a file exceeds 8MB.");
            }

            $mime = $finfo->file($tmps[$j]) ?: 'application/octet-stream';
            if (!in_array($mime, $ALLOWED_MIME, true)) {
                throw new RuntimeException("unsupported file type.");
            }
        }

        // Second pass: move + insert rows
        for ($j = 0; $j < count($tmps); $j++) {
            if (empty($tmps[$j])) continue;

            $mime = $finfo->file($tmps[$j]) ?: 'application/octet-stream';
            $orig = $names[$j] ?? 'file';
            $ext  = safe_ext($orig, $ALLOWED_EXT);
            $fname = strtoupper(bin2hex(random_bytes(8))) . '.' . $ext;

            $target_path = $base_upload_dir . $fname;
            if (!move_uploaded_file($tmps[$j], $target_path)) {
                throw new RuntimeException("failed to save an uploaded file.");
            }

            $rel_path = "{$year}/{$month}/{$batch_ref}/{$fname}";
            $size_int = (int)$sizes[$j];

            $image_stmt->bind_param("isssi", $trade_id, $rel_path, $orig, $mime, $size_int);
            $image_stmt->execute();
        }

        $conn->commit();

        $cards_created++;
        $total_estimate += $est_payout;

    } catch (Throwable $e) {
        try { $conn->rollback(); } catch (Throwable $ignore) {}
        $cards_failed++;
        $error_messages[] = "Card #".($i+1).": " . $e->getMessage();
        continue;
    }
}

// ===== Cleanup =====
$gc_stmt->close();
$trade_stmt->close();
$image_stmt->close();

// ===== Feedback =====
if ($cards_created > 0 && $cards_failed === 0) {
    $_SESSION['success'] =
        "{$cards_created} card(s) submitted under batch <strong>{$batch_ref}</strong>. " .
        "Estimated total payout: <strong>" . format_ngn($total_estimate) . "</strong>.<br>" .
        "<a href=\"../../user/giftcards/card_transactions.php\" class=\"btn btn-sm btn-outline-light mt-2\">
            View my transactions
         </a>";
} elseif ($cards_created > 0 && $cards_failed > 0) {
    $_SESSION['success'] =
        "{$cards_created} card(s) submitted under batch <strong>{$batch_ref}</strong>. " .
        "Estimated total payout: <strong>" . format_ngn($total_estimate) . "</strong>.<br>" .
        "<a href=\"../../user/giftcards/card_transactions.php\" class=\"btn btn-sm btn-outline-light mt-2\">
            View my transactions
         </a>";
    $_SESSION['error']   = "Some cards failed: " . htmlspecialchars(implode(' ', $error_messages));
} else {
    $_SESSION['error'] = "No valid cards were processed. " . htmlspecialchars(implode(' ', $error_messages));
}

header("Location: ../../user/giftcards/submit_card.php");
exit;

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


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