SelfRegistrationHandler.php

17.92 KB
08/07/2025 12:55
PHP
SelfRegistrationHandler.php
<?php

require_once __DIR__.'/../Core/SecurityHelper.php';
require_once __DIR__.'/../Core/RateLimiter.php';

class SelfRegistrationHandler
{
    /**
     * @var mixed
     */
    private $db;
    /**
     * @var mixed
     */
    private $telegram;
    /**
     * @var mixed
     */
    private $registrationStepsDir;

    /**
     * @param $database
     * @param $telegramService
     */
    public function __construct($database, $telegramService)
    {
        $this->db = $database;
        $this->telegram = $telegramService;
        $this->registrationStepsDir = __DIR__.'/../../logs';

        // สร้าง directory ถ้ายังไม่มี
        if (!is_dir($this->registrationStepsDir)) {
            mkdir($this->registrationStepsDir, 0755, true);
        }
    }

    /**
     * ตรวจสอบว่าผู้ใช้สามารถใช้บอทได้หรือไม่
     */
    public function canUserAccessBot($chatUserId)
    {
        // ตรวจสอบว่าผู้ใช้ลงทะเบียนแล้วหรือยัง
        $stmt = $this->db->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$chatUserId]);
        $employee = $stmt->fetch();

        if ($employee) {
            return true; // ลงทะเบียนแล้ว
        }

        // ถ้ายังไม่ลงทะเบียน ตรวจสอบว่าเปิดการลงทะเบียนด้วยตัวเองหรือไม่
        return ALLOW_SELF_REGISTRATION;
    }

    /**
     * เริ่มกระบวนการลงทะเบียน
     */
    public function startRegistration($chatId, $userId)
    {
        if (!ALLOW_SELF_REGISTRATION) {
            return $this->telegram->sendMessage($chatId,
                "❌ ขออภัย การลงทะเบียนด้วยตัวเองถูกปิดใช้งาน\n".
                "กรุณาติดต่อผู้ดูแลระบบเพื่อเพิ่มข้อมูลของคุณ"
            );
        }

        // ตรวจสอบว่าผู้ใช้ลงทะเบียนแล้วหรือยัง
        $stmt = $this->db->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$userId]);
        if ($stmt->fetch()) {
            return $this->telegram->sendMessage($chatId, "✅ คุณได้ลงทะเบียนในระบบแล้ว");
        }

        // เริ่มการลงทะเบียน
        $this->saveRegistrationStep($userId, $chatId, 'first_name', []);

        return $this->telegram->sendMessage($chatId,
            "🎯 *การลงทะเบียนในระบบลา*\n\n".
            "กรุณากรอกข้อมูลตามขั้นตอน:\n\n".
            "1️⃣ *ชื่อจริง* (ภาษาไทยหรือภาษาอังกฤษ)\n".
            "กรุณาพิมพ์ชื่อจริงของคุณ:",
            null,
            'Markdown'
        );
    }

    /**
     * จัดการข้อมูลที่ผู้ใช้ส่งมา
     */
    public function handleUserInput($chatId, $userId, $messageText)
    {
        // ตรวจสอบ rate limit
        if (!RateLimiter::checkRateLimit($userId)) {
            SecurityHelper::logSecurityEvent('registration_rate_limit', $userId);
            $this->telegram->sendMessage($chatId,
                "⚠️ คุณส่งข้อความบ่อยเกินไป กรุณารอสักครู่แล้วลองใหม่"
            );
            return true; // จัดการแล้ว
        }

        $step = $this->getCurrentRegistrationStep($userId, $chatId);

        if (!$step) {
            return false; // ไม่อยู่ในกระบวนการลงทะเบียน
        }

        // กรองข้อมูล input
        $messageText = SecurityHelper::sanitizeInput($messageText, 'text');

        // ตรวจสอบความยาว
        if (strlen($messageText) > (defined('MAX_INPUT_LENGTH') ? MAX_INPUT_LENGTH : 500)) {
            $this->telegram->sendMessage($chatId,
                "❌ ข้อความยาวเกินไป กรุณากรอกข้อมูลให้สั้นลง"
            );
            return true;
        }

        $stepData = $step['data'];

        switch ($step['currentStep']) {
            case 'first_name':
                return $this->handleFirstName($chatId, $userId, $messageText, $stepData);

            case 'last_name':
                return $this->handleLastName($chatId, $userId, $messageText, $stepData);

            case 'email':
                return $this->handleEmail($chatId, $userId, $messageText, $stepData);

            case 'confirm':
                return $this->handleConfirmation($chatId, $userId, $messageText, $stepData);

            default:
                $this->clearRegistrationStep($userId, $chatId);
                return false;
        }
    }

    /**
     * @param $chatId
     * @param $userId
     * @param $firstName
     * @param $stepData
     * @return mixed
     */
    private function handleFirstName($chatId, $userId, $firstName, $stepData)
    {
        $firstName = SecurityHelper::sanitizeInput($firstName, 'name');

        if (!SecurityHelper::validateInput($firstName, 'name', 2, 50)) {
            SecurityHelper::logSecurityEvent('invalid_first_name', $userId, ['input' => $firstName]);
            return $this->telegram->sendMessage($chatId,
                "❌ กรุณากรอกชื่อจริงให้ถูกต้อง (2-50 ตัวอักษร, ใช้ได้เฉพาะตัวอักษรไทย-อังกฤษ):"
            );
        }

        $stepData['first_name'] = $firstName;
        $this->saveRegistrationStep($userId, $chatId, 'last_name', $stepData);

        $safeFirstName = SecurityHelper::escapeMarkdown($firstName);
        return $this->telegram->sendMessage($chatId,
            "✅ รับทราบชื่อจริง: *{$safeFirstName}*\n\n".
            "2️⃣ *นามสกุล* (ภาษาไทยหรือภาษาอังกฤษ)\n".
            "กรุณาพิมพ์นามสกุลของคุณ:",
            null,
            'Markdown'
        );
    }

    /**
     * @param $chatId
     * @param $userId
     * @param $lastName
     * @param $stepData
     * @return mixed
     */
    private function handleLastName($chatId, $userId, $lastName, $stepData)
    {
        $lastName = SecurityHelper::sanitizeInput($lastName, 'name');

        if (!SecurityHelper::validateInput($lastName, 'name', 2, 50)) {
            SecurityHelper::logSecurityEvent('invalid_last_name', $userId, ['input' => $lastName]);
            return $this->telegram->sendMessage($chatId,
                "❌ กรุณากรอกนามสกุลให้ถูกต้อง (2-50 ตัวอักษร, ใช้ได้เฉพาะตัวอักษรไทย-อังกฤษ):"
            );
        }

        $stepData['last_name'] = $lastName;
        $this->saveRegistrationStep($userId, $chatId, 'email', $stepData);

        $safeLastName = SecurityHelper::escapeMarkdown($lastName);
        return $this->telegram->sendMessage($chatId,
            "✅ รับทราบนามสกุล: *{$safeLastName}*\n\n".
            "3️⃣ *อีเมล* (สำหรับการติดต่อ)\n".
            "กรุณาพิมพ์อีเมลของคุณ:",
            null,
            'Markdown'
        );
    }

    /**
     * @param $chatId
     * @param $userId
     * @param $email
     * @param $stepData
     * @return mixed
     */
    private function handleEmail($chatId, $userId, $email, $stepData)
    {
        $email = SecurityHelper::sanitizeInput($email, 'email');
        $email = strtolower(trim($email));

        if (!SecurityHelper::validateInput($email, 'email', 5, 100)) {
            SecurityHelper::logSecurityEvent('invalid_email', $userId, ['input' => $email]);
            return $this->telegram->sendMessage($chatId,
                "❌ รูปแบบอีเมลไม่ถูกต้อง กรุณาลองใหม่:"
            );
        }

        // ตรวจสอบว่าอีเมลนี้มีคนใช้แล้วหรือยัง
        $stmt = $this->db->prepare("SELECT id FROM employees WHERE email = ?");
        $stmt->execute([$email]);
        if ($stmt->fetch()) {
            SecurityHelper::logSecurityEvent('email_already_exists', $userId, ['email' => $email]);
            return $this->telegram->sendMessage($chatId,
                "❌ อีเมลนี้มีคนใช้แล้วในระบบ กรุณาใช้อีเมลอื่น:"
            );
        }

        $stepData['email'] = $email;
        $this->saveRegistrationStep($userId, $chatId, 'confirm', $stepData);

        $safeFirstName = SecurityHelper::escapeMarkdown($stepData['first_name']);
        $safeLastName = SecurityHelper::escapeMarkdown($stepData['last_name']);
        $safeEmail = SecurityHelper::escapeMarkdown($email);

        $confirmMessage = "📋 *ยืนยันข้อมูลการลงทะเบียน*\n\n".
            "ชื่อจริง: *{$safeFirstName}*\n".
            "นามสกุล: *{$safeLastName}*\n".
            "อีเมล: *{$safeEmail}*\n\n".
            "❓ ข้อมูลถูกต้องหรือไม่?\n\n".
            "💬 พิมพ์ `ถูกต้อง` หรือ `ใช่` เพื่อยืนยัน\n".
            "💬 พิมพ์ `ยกเลิก` เพื่อยกเลิกการลงทะเบียน";

        return $this->telegram->sendMessage($chatId, $confirmMessage, null, 'Markdown');
    }

    /**
     * @param $chatId
     * @param $userId
     * @param $confirmation
     * @param $stepData
     * @return mixed
     */
    private function handleConfirmation($chatId, $userId, $confirmation, $stepData)
    {
        $confirmation = trim(strtolower($confirmation));

        if ($confirmation === 'ยกเลิก' || $confirmation === 'cancel') {
            $this->clearRegistrationStep($userId, $chatId);
            return $this->telegram->sendMessage($chatId,
                "❌ ยกเลิกการลงทะเบียนแล้ว\n\n".
                "หากต้องการลงทะเบียนใหม่ พิมพ์ /register"
            );
        }

        if ($confirmation === 'ถูกต้อง' || $confirmation === 'ใช่' || $confirmation === 'yes' || $confirmation === 'confirm') {
            return $this->createEmployee($chatId, $userId, $stepData);
        }

        return $this->telegram->sendMessage($chatId,
            "❓ กรุณาพิมพ์ `ถูกต้อง` หรือ `ยกเลิก` เท่านั้น:"
        );
    }

    /**
     * @param $chatId
     * @param $userId
     * @param $stepData
     */
    private function createEmployee($chatId, $userId, $stepData)
    {
        try {
            // สร้างรหัสพนักงานอัตโนมัติ (ถ้าเปิดการตั้งค่า)
            $employeeCode = null;
            if (AUTO_GENERATE_EMPLOYEE_CODE) {
                $employeeCode = $this->generateEmployeeCode();
            }

            // เพิ่มข้อมูลพนักงานใหม่
            $stmt = $this->db->prepare("
                INSERT INTO employees (employee_code, chat_user_id, first_name, last_name, email, manager_id, is_manager, is_hr, created_at)
                VALUES (?, ?, ?, ?, ?, ?, 0, 0, NOW())
            ");

            $result = $stmt->execute([
                $employeeCode,
                $userId,
                $stepData['first_name'],
                $stepData['last_name'],
                $stepData['email'],
                DEFAULT_MANAGER_ID
            ]);

            if ($result) {
                $this->clearRegistrationStep($userId, $chatId);

                $successMessage = "🎉 *ลงทะเบียนสำเร็จ!*\n\n".
                    "✅ ยินดีต้อนรับสู่ระบบลา\n\n".
                    "ข้อมูลของคุณ:\n".
                    "👤 ชื่อ: {$stepData['first_name']} {$stepData['last_name']}\n".
                    "📧 อีเมล: {$stepData['email']}\n";

                if ($employeeCode) {
                    $successMessage .= "🏷️ รหัสพนักงาน: {$employeeCode}\n";
                }

                $successMessage .= "\n💡 พิมพ์ /help เพื่อดูคำสั่งที่ใช้งานได้";

                return $this->telegram->sendMessage($chatId, $successMessage, null, 'Markdown');
            } else {
                throw new Exception("ไม่สามารถบันทึกข้อมูลได้");
            }

        } catch (Exception $e) {
            error_log("Registration error: ".$e->getMessage());
            return $this->telegram->sendMessage($chatId,
                "❌ เกิดข้อผิดพลาดในการลงทะเบียน กรุณาลองใหม่อีกครั้ง\n\n".
                "พิมพ์ /register เพื่อเริ่มต้นใหม่"
            );
        }
    }

    private function generateEmployeeCode()
    {
        // สร้างรหัสพนักงานแบบ EMP001, EMP002, ...
        $stmt = $this->db->prepare("
            SELECT employee_code
            FROM employees
            WHERE employee_code LIKE ?
            ORDER BY employee_code DESC
            LIMIT 1
        ");

        $prefix = EMPLOYEE_CODE_PREFIX.'%';
        $stmt->execute([$prefix]);
        $lastCode = $stmt->fetchColumn();

        if ($lastCode) {
            // แยกหมายเลขออกมา
            $number = (int) substr($lastCode, strlen(EMPLOYEE_CODE_PREFIX));
            $newNumber = $number + 1;
        } else {
            $newNumber = 1;
        }

        return EMPLOYEE_CODE_PREFIX.str_pad($newNumber, 3, '0', STR_PAD_LEFT);
    }

    // Methods สำหรับจัดการ registration steps
    /**
     * @param $userId
     * @param $chatId
     * @param $step
     * @param $data
     */
    private function saveRegistrationStep($userId, $chatId, $step, $data)
    {
        $secureUserId = SecurityHelper::securePath($userId);
        $secureChatId = SecurityHelper::securePath($chatId);
        $filename = $this->registrationStepsDir."/registration_{$secureUserId}_{$secureChatId}.json";

        $stepData = [
            'currentStep' => $step,
            'data' => $data,
            'timestamp' => time(),
            'hash' => SecurityHelper::generateHash($userId.$chatId.$step)
        ];

        // เข้ารหัสข้อมูลก่อนบันทึก
        $encryptedData = base64_encode(json_encode($stepData));

        if (!file_put_contents($filename, $encryptedData, LOCK_EX)) {
            SecurityHelper::logSecurityEvent('registration_save_failed', $userId);
            error_log("Failed to save registration step for user {$userId}");
        }
    }

    /**
     * @param $userId
     * @param $chatId
     */
    private function getCurrentRegistrationStep($userId, $chatId)
    {
        $secureUserId = SecurityHelper::securePath($userId);
        $secureChatId = SecurityHelper::securePath($chatId);
        $filename = $this->registrationStepsDir."/registration_{$secureUserId}_{$secureChatId}.json";

        if (!file_exists($filename)) {
            return null;
        }

        $content = file_get_contents($filename);
        if (!$content) {
            return null;
        }

        // ถอดรหัสข้อมูล
        $decodedContent = base64_decode($content);
        $stepData = json_decode($decodedContent, true);

        if (!$stepData || !isset($stepData['timestamp'])) {
            $this->clearRegistrationStep($userId, $chatId);
            return null;
        }

        // ตรวจสอบ hash เพื่อความปลอดภัย
        $expectedHash = SecurityHelper::generateHash($userId.$chatId.$stepData['currentStep']);
        if (!isset($stepData['hash']) || $stepData['hash'] !== $expectedHash) {
            SecurityHelper::logSecurityEvent('registration_hash_mismatch', $userId);
            $this->clearRegistrationStep($userId, $chatId);
            return null;
        }

        // ตรวจสอบว่าข้อมูลหมดอายุหรือยัง (30 นาที)
        if (time() - $stepData['timestamp'] > 1800) {
            $this->clearRegistrationStep($userId, $chatId);
            return null;
        }

        return $stepData;
    }

    /**
     * @param $userId
     * @param $chatId
     */
    private function clearRegistrationStep($userId, $chatId)
    {
        $filename = $this->registrationStepsDir."/registration_{$userId}_{$chatId}.json";
        if (file_exists($filename)) {
            unlink($filename);
        }
    }

    /**
     * ยกเลิกการลงทะเบียน
     */
    public function cancelRegistration($chatId, $userId)
    {
        $this->clearRegistrationStep($userId, $chatId);
        return $this->telegram->sendMessage($chatId,
            "❌ ยกเลิกการลงทะเบียนแล้ว\n\n".
            "หากต้องการลงทะเบียนใหม่ พิมพ์ /register"
        );
    }
}