Загрузка данных


<?php
require_once __DIR__ . '/includes/init.php';

if (is_logged_in()) {
    redirect(url('profile.php'));
}

const LOGIN_MAX_ATTEMPTS = 5;
const LOGIN_LOCK_MINUTES = 10;
const LOGIN_WINDOW_MINUTES = 10;

function login_get_ip(): string
{
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

function login_get_lock_info(PDO $pdo, string $email, string $ipAddress): ?array
{
    $stmt = $pdo->prepare('
        SELECT 
            locked_until,
            TIMESTAMPDIFF(SECOND, NOW(), locked_until) AS seconds_left
        FROM login_attempts
        WHERE email = :email
          AND ip_address = :ip_address
          AND locked_until IS NOT NULL
          AND locked_until > NOW()
        ORDER BY locked_until DESC
        LIMIT 1
    ');

    $stmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);

    $lock = $stmt->fetch();

    return $lock ?: null;
}

function login_count_failed_attempts(PDO $pdo, string $email, string $ipAddress): int
{
    $stmt = $pdo->prepare('
        SELECT COUNT(*) AS total
        FROM login_attempts
        WHERE email = :email
          AND ip_address = :ip_address
          AND is_success = 0
          AND locked_until IS NULL
          AND attempted_at >= DATE_SUB(NOW(), INTERVAL ' . LOGIN_WINDOW_MINUTES . ' MINUTE)
    ');

    $stmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);

    $row = $stmt->fetch();

    return (int)($row['total'] ?? 0);
}

function login_record_failed_attempt(PDO $pdo, string $email, string $ipAddress): ?string
{
    $failedAttempts = login_count_failed_attempts($pdo, $email, $ipAddress) + 1;

    if ($failedAttempts >= LOGIN_MAX_ATTEMPTS) {
        $stmt = $pdo->prepare('
            INSERT INTO login_attempts (
                email,
                ip_address,
                is_success,
                locked_until
            ) VALUES (
                :email,
                :ip_address,
                0,
                DATE_ADD(NOW(), INTERVAL ' . LOGIN_LOCK_MINUTES . ' MINUTE)
            )
        ');

        $stmt->execute([
            'email' => $email,
            'ip_address' => $ipAddress
        ]);

        return 'Слишком много неправильных попыток. Вход заблокирован на ' . LOGIN_LOCK_MINUTES . ' минут.';
    }

    $stmt = $pdo->prepare('
        INSERT INTO login_attempts (
            email,
            ip_address,
            is_success
        ) VALUES (
            :email,
            :ip_address,
            0
        )
    ');

    $stmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);

    $leftAttempts = LOGIN_MAX_ATTEMPTS - $failedAttempts;

    return 'Неверный email или пароль. Осталось попыток: ' . $leftAttempts . '.';
}

function login_record_success(PDO $pdo, string $email, string $ipAddress): void
{
    $deleteStmt = $pdo->prepare('
        DELETE FROM login_attempts
        WHERE email = :email
          AND ip_address = :ip_address
          AND is_success = 0
    ');

    $deleteStmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);

    $insertStmt = $pdo->prepare('
        INSERT INTO login_attempts (
            email,
            ip_address,
            is_success
        ) VALUES (
            :email,
            :ip_address,
            1
        )
    ');

    $insertStmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);
}

if (is_post()) {
    csrf_validate();

    $email = mb_strtolower(trim($_POST['email'] ?? ''));
    $password = $_POST['password'] ?? '';
    $ipAddress = login_get_ip();

    if ($email === '' || $password === '') {
        flash_set('danger', 'Введите email и пароль.');
        redirect(url('login.php'));
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        flash_set('danger', 'Введите корректный email.');
        redirect(url('login.php'));
    }

    $lock = login_get_lock_info($pdo, $email, $ipAddress);

    if ($lock) {
        $secondsLeft = max(1, (int)$lock['seconds_left']);
        $minutesLeft = (int)ceil($secondsLeft / 60);

        flash_set('danger', 'Вход временно заблокирован. Повторите попытку через ' . $minutesLeft . ' мин.');
        redirect(url('login.php'));
    }

    $stmt = $pdo->prepare('
        SELECT
            u.id,
            u.full_name,
            u.email,
            u.password_hash,
            u.status,
            r.code AS role_code
        FROM users u
        JOIN roles r ON r.id = u.role_id
        WHERE u.email = :email
        LIMIT 1
    ');

    $stmt->execute([
        'email' => $email
    ]);

    $user = $stmt->fetch();

    if (!$user || !password_verify($password, $user['password_hash'])) {
        $message = login_record_failed_attempt($pdo, $email, $ipAddress);
        flash_set('danger', $message);
        redirect(url('login.php'));
    }

    if ($user['status'] !== 'active') {
        flash_set('danger', 'Учётная запись недоступна. Обратитесь к администратору.');
        redirect(url('login.php'));
    }

    login_record_success($pdo, $email, $ipAddress);

    session_regenerate_id(true);

    $_SESSION['user_id'] = (int)$user['id'];

    $updateStmt = $pdo->prepare('
        UPDATE users
        SET last_login_at = NOW()
        WHERE id = :id
    ');

    $updateStmt->execute([
        'id' => $user['id']
    ]);

    flash_set('success', 'Вы успешно вошли в систему.');

    if ($user['role_code'] === 'admin') {
        redirect(url('admin/index.php'));
    }

    if ($user['role_code'] === 'manager') {
        redirect(url('manager/index.php'));
    }

    redirect(url('profile.php'));
}

require_once __DIR__ . '/includes/header.php';
?>

<div class="auth-page">
    <form method="post" class="auth-card">
        <?= csrf_field() ?>

        <h1>Вход</h1>

        <p class="text-muted">
            Войдите в аккаунт для оформления заявок и работы с личным кабинетом.
        </p>

        <div class="mb-3">
            <label class="form-label">Email</label>
            <input
                type="email"
                name="email"
                class="form-control"
                required
                autocomplete="email"
            >
        </div>

        <div class="mb-3">
            <label class="form-label">Пароль</label>
            <input
                type="password"
                name="password"
                class="form-control"
                required
                autocomplete="current-password"
            >
        </div>

        <button type="submit" class="btn btn-primary w-100">
            Войти
        </button>

        <div class="auth-links">
            <a href="<?= e(url('register.php')) ?>">Создать аккаунт</a>
        </div>
    </form>
</div>

<?php require_once __DIR__ . '/includes/footer.php'; ?>






<a href="product.php?id=<?= e($product['id']) ?>" class="product-image-link">
    <?php if ((int)$product['stock_quantity'] <= 0): ?>
        <span class="stock-label stock-label-out">
            Нет в наличии
        </span>
    <?php elseif ((int)$product['stock_quantity'] <= 5): ?>
        <span class="stock-label stock-label-low">
            Осталось мало
        </span>
    <?php endif; ?>

    <?php if ($product['main_image']): ?>
        <img
            src="<?= e(url($product['main_image'])) ?>"
            alt="<?= e($product['name']) ?>"
            class="product-card-image"
        >
    <?php else: ?>
        <div class="product-no-image">
            Нет изображения
        </div>
    <?php endif; ?>
</a>







<div class="product-info-row">
    <span>Остаток:</span>

    <?php if ((int)$product['stock_quantity'] <= 0): ?>
        <strong class="stock-text-out">Нет в наличии</strong>
    <?php else: ?>
        <strong><?= e($product['stock_quantity']) ?> шт.</strong>
    <?php endif; ?>
</div>







<div class="product-actions-box">
    <?php if ((int)$product['stock_quantity'] <= 0): ?>
        <div class="summary-warning">
            Товара сейчас нет в наличии. Добавление в заявку временно недоступно.
        </div>
    <?php elseif (is_logged_in()): ?>
        <form method="post" action="<?= e(url('cart.php')) ?>" class="add-to-cart-form">
            <?= csrf_field() ?>

            <input type="hidden" name="form_action" value="add">
            <input type="hidden" name="product_id" value="<?= e($product['id']) ?>">

            <div class="mb-3">
                <label class="form-label">Количество</label>
                <input
                    type="number"
                    name="quantity"
                    class="form-control"
                    min="<?= e($product['min_order_qty']) ?>"
                    max="<?= e($product['stock_quantity']) ?>"
                    value="<?= e($product['min_order_qty']) ?>"
                >

                <div class="help-text">
                    Минимальное количество: <?= e($product['min_order_qty']) ?> шт.
                </div>
            </div>

            <button type="submit" class="btn btn-primary">
                Добавить в заявку
            </button>
        </form>
    <?php else: ?>
        <p class="text-muted">
            Чтобы добавить товар в заявку, войдите в аккаунт.
        </p>

        <a href="<?= e(url('login.php')) ?>" class="btn btn-primary">
            Войти
        </a>
    <?php endif; ?>

    <a href="<?= e(url('catalog.php')) ?>" class="btn btn-outline">
        Вернуться в каталог
    </a>
</div>








.product-image-link {
    position: relative;
}

.stock-label {
    position: absolute;
    top: 14px;
    left: 14px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    min-height: 30px;
    padding: 6px 12px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 900;
    box-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);
}

.stock-label-out {
    background: #fdecec;
    color: #dc3545;
}

.stock-label-low {
    background: #fff8e1;
    color: #9a6b00;
}

.stock-text-out {
    color: #dc3545;
}