webhook_handler.php

23.94 KB
08/07/2025 12:55
PHP
webhook_handler.php
<?php

// header('Content-Type: application/json');

require_once __DIR__.'/config/telegram_config.php';
require_once __DIR__.'/config/db_config.php';
require_once __DIR__.'/src/Core/Database.php';
require_once __DIR__.'/src/Services/TelegramService.php';
require_once __DIR__.'/src/Models/LeaveType.php';
require_once __DIR__.'/src/Models/Employee.php';
require_once __DIR__.'/src/Models/LeaveRequest.php';
require_once __DIR__.'/src/Models/LeaveBalance.php';
require_once __DIR__.'/src/Handlers/LeaveApplicationHandler.php';
require_once __DIR__.'/src/Handlers/BalanceCheckHandler.php';
require_once __DIR__.'/src/Handlers/CancelRequestHandler.php';
require_once __DIR__.'/src/Handlers/ManagerActionHandler.php';
require_once __DIR__.'/src/Handlers/HrSummaryHandler.php';
require_once __DIR__.'/src/Handlers/SelfRegistrationHandler.php';

use App\Core\Database;
use App\Handlers\BalanceCheckHandler;
use App\Handlers\CancelRequestHandler;
use App\Handlers\HrSummaryHandler;
use App\Handlers\LeaveApplicationHandler;
use App\Handlers\ManagerActionHandler;
use App\Services\TelegramService;

// PHP 7.4 compatibility functions
if (!function_exists('str_starts_with')) {
    /**
     * @param string $haystack
     * @param string $needle
     */
    function str_starts_with(string $haystack, string $needle): bool
    {
        return strpos($haystack, $needle) === 0;
    }
}

if (!function_exists('str_ends_with')) {
    /**
     * @param string $haystack
     * @param string $needle
     */
    function str_ends_with(string $haystack, string $needle): bool
    {
        return substr($haystack, -strlen($needle)) === $needle;
    }
}

if (!function_exists('str_contains')) {
    /**
     * @param string $haystack
     * @param string $needle
     */
    function str_contains(string $haystack, string $needle): bool
    {
        return strpos($haystack, $needle) !== false;
    }
}

if (!function_exists('log_message')) {
    /**
     * @param string $message
     * @return null
     */
    function log_message(string $message): void
    {
        $logFile = __DIR__.'/logs/webhook.log';
        if (!file_exists(dirname($logFile))) {
            if (!mkdir(dirname($logFile), 0775, true) && !is_dir(dirname($logFile))) {
                return;
            }
        }
        $timestamp = date("Y-m-d H:i:s");
        file_put_contents($logFile, "[{$timestamp}] {$message}\n", FILE_APPEND);
    }
}

$input = file_get_contents('php://input');

// สำหรับการทดสอบใน CLI mode
if (empty($input) && php_sapi_name() === 'cli') {
    log_message("CLI mode detected, reading from STDIN");
    $input = stream_get_contents(STDIN);
    log_message("STDIN input: ".$input);
}

log_message("Total input received: ".strlen($input)." characters");

if (empty($input)) {
    log_message("Webhook received empty input.");
    http_response_code(400);
    echo json_encode(['status' => 'error', 'message' => 'No input received.']);
    exit;
}

$update = json_decode($input, true);
if (json_last_error() !== JSON_ERROR_NONE) {
    log_message("Webhook received invalid JSON: ".json_last_error_msg()." | Input: ".$input);
    http_response_code(400);
    echo json_encode(['status' => 'error', 'message' => 'Invalid JSON.']);
    exit;
}

if (isset($update['message']['message_id'])) {
    log_message("Update ID: {$update['update_id']}, Message ID: {$update['message']['message_id']}, Chat ID: {$update['message']['chat']['id']}, User ID: {$update['message']['from']['id']}, Text: ".($update['message']['text'] ?? "[no text]"));
} elseif (isset($update['callback_query']['id'])) {
    log_message("Update ID: {$update['update_id']}, Callback Query ID: {$update['callback_query']['id']}, Chat ID: {$update['callback_query']['message']['chat']['id']}, User ID: {$update['callback_query']['from']['id']}, Data: ".($update['callback_query']['data'] ?? "[no data]"));
}

$telegramService = new TelegramService();

// สร้าง Database connection และ SelfRegistrationHandler
$database = Database::getInstance();
$pdo = $database->getConnection();
$selfRegistrationHandler = new SelfRegistrationHandler($pdo, $telegramService);

log_message("TelegramService and SelfRegistrationHandler initialized successfully");

/**
 * @param int $userId
 * @param int $chatId
 */
function getUserState(int $userId, int $chatId): ?string
{
    $stateFile = __DIR__."/logs/user_{$userId}_chat_{$chatId}_state.txt";
    if (file_exists($stateFile)) {
        return trim(file_get_contents($stateFile));
    }
    return null;
}

/**
 * @param int $userId
 * @param int $chatId
 */
function clearUserStateWebhook(int $userId, int $chatId): void
{
    $stateFile = __DIR__."/logs/user_{$userId}_chat_{$chatId}_state.txt";
    if (file_exists($stateFile)) {
        if (unlink($stateFile)) {
            log_message("State file cleared by webhook_handler for user {$userId} chat {$chatId}: {$stateFile}");
        } else {
            log_message("Failed to clear state file by webhook_handler for user {$userId} chat {$chatId}: {$stateFile}");
        }
    }
}

if (isset($update['message'])) {
    $message = $update['message'];
    $chatId = $message['chat']['id'];
    $userId = $message['from']['id'];
    $firstName = $message['from']['first_name'] ?? 'User';
    $text = $message['text'] ?? '';
    $lowerText = strtolower($text);

    // ตรวจสอบสิทธิ์การเข้าใช้งาน
    $canAccess = $selfRegistrationHandler->canUserAccessBot($userId);
    log_message("Access check for user {$userId}: ".($canAccess ? "ALLOWED" : "DENIED"));

    if (!$canAccess) {
        $responseMsg = "❌ ขออภัย คุณยังไม่ได้รับอนุญาตให้ใช้บอทนี้\nการลงทะเบียนด้วยตัวเองถูกปิดใช้งาน กรุณาติดต่อผู้ดูแลระบบ";
        $result = $telegramService->sendMessage($chatId, $responseMsg);
        log_message("Access denied response sent to user {$userId}. Result: ".json_encode($result));
        exit;
    }

    // ตรวจสอบว่าอยู่ในกระบวนการลงทะเบียนหรือไม่
    $registrationHandled = $selfRegistrationHandler->handleUserInput($chatId, $userId, $text);
    log_message("Registration handler result for user {$userId}: ".($registrationHandled ? "HANDLED" : "NOT_HANDLED"));

    if ($registrationHandled) {
        log_message("Registration process handled, exiting for user {$userId}");
        exit; // หยุดการประมวลผลเมื่อจัดการกับกระบวนการลงทะเบียน
    }

    $userState = getUserState($userId, $chatId);

    $currentMode = 'leave';
    if ($userState) {
        if (str_starts_with($userState, 'wfh_')) {
            $currentMode = 'wfh';
        }
    } elseif ($lowerText === '/wfh') {
        $currentMode = 'wfh';
    }

    if ($userState) {
        if (str_starts_with($userState, 'cancelReq_confirm_')) {
            $telegramService->sendMessage($chatId, "น้องลารอการยืนยันการยกเลิกจากปุ่มค่ะ กรุณากดปุ่มที่แสดงผลไปก่อนหน้านี้นะคะ หรือพิมพ์ /cancel เพื่อยกเลิกทั้งหมด");
        } elseif (str_starts_with($userState, 'la_') || str_starts_with($userState, 'wfh_')) {
            // ตรวจสอบว่าผู้ใช้ลงทะเบียนแล้ว
            $stmt = $pdo->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
            $stmt->execute([$userId]);
            if (!$stmt->fetch()) {
                $telegramService->sendMessage($chatId, "❌ กรุณาลงทะเบียนก่อนใช้งาน พิมพ์ /register");
                clearUserStateWebhook($userId, $chatId);
                exit;
            }

            $leaveAppHandler = new LeaveApplicationHandler($telegramService, $chatId, $userId, $currentMode);
            if (str_starts_with($userState, 'la_expect_startDate_for_') || str_starts_with($userState, 'wfh_expect_startDate_for_')) {
                $leaveAppHandler->handleStartDateInput($userState, $text);
            } elseif (str_starts_with($userState, 'la_expect_endDate_for_') || str_starts_with($userState, 'wfh_expect_endDate_for_')) {
                $leaveAppHandler->handleEndDateInput($userState, $text);
            } elseif (str_starts_with($userState, 'la_expect_reason_for_') || str_starts_with($userState, 'wfh_expect_reason_for_')) {
                $leaveAppHandler->handleReasonInput($userState, $text);
            } elseif (str_starts_with($userState, 'la_confirm_') || str_starts_with($userState, 'wfh_confirm_')) {
                $telegramService->sendMessage($chatId, "น้องลารอการยืนยันการขอลา/WFH จากปุ่มค่ะ กรุณากดปุ่มที่แสดงผลไปก่อนหน้านี้นะคะ หรือพิมพ์ /cancel เพื่อยกเลิกทั้งหมด");
            } else {
                log_message("Unhandled state '{$userState}' for user {$userId} with text input '{$text}'. No action taken.");
            }
        } else {
            log_message("Unknown state prefix for user {$userId}: '{$userState}'. No action taken.");
        }
    }
    // คำสั่งใหม่เมื่อไม่มี state ที่ active
    else if ($lowerText === '/ลา' || $lowerText === '/leave') {
        // ตรวจสอบว่าผู้ใช้ลงทะเบียนแล้ว
        $stmt = $pdo->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$userId]);
        if (!$stmt->fetch()) {
            $telegramService->sendMessage($chatId, "❌ กรุณาลงทะเบียนก่อนใช้งาน พิมพ์ /register");
            exit;
        }

        $leaveAppHandler = new LeaveApplicationHandler($telegramService, $chatId, $userId, 'leave');
        $leaveAppHandler->initiateRequest();
    } elseif ($lowerText === '/wfh') {
        // ตรวจสอบว่าผู้ใช้ลงทะเบียนแล้ว
        $stmt = $pdo->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$userId]);
        if (!$stmt->fetch()) {
            $telegramService->sendMessage($chatId, "❌ กรุณาลงทะเบียนก่อนใช้งาน พิมพ์ /register");
            exit;
        }

        $leaveAppHandler = new LeaveApplicationHandler($telegramService, $chatId, $userId, 'wfh');
        $leaveAppHandler->initiateRequest();
    } elseif ($lowerText === '/เช็คยอด' || $lowerText === '/balance' || $lowerText === '/checkbalance') {
        // ตรวจสอบว่าผู้ใช้ลงทะเบียนแล้ว
        $stmt = $pdo->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$userId]);
        if (!$stmt->fetch()) {
            $telegramService->sendMessage($chatId, "❌ กรุณาลงทะเบียนก่อนใช้งาน พิมพ์ /register");
            exit;
        }

        $balanceHandler = new BalanceCheckHandler($telegramService, $chatId, $userId);
        $balanceHandler->showBalances();
        clearUserStateWebhook($userId, $chatId);
    } elseif ($lowerText === '/ยกเลิกคำขอ' || $lowerText === '/cancelrequest') {
        // ตรวจสอบว่าผู้ใช้ลงทะเบียนแล้ว
        $stmt = $pdo->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$userId]);
        if (!$stmt->fetch()) {
            $telegramService->sendMessage($chatId, "❌ กรุณาลงทะเบียนก่อนใช้งาน พิมพ์ /register");
            exit;
        }

        $cancelHandler = new CancelRequestHandler($telegramService, $chatId, $userId);
        $cancelHandler->startCancellationProcess();
    } elseif ($lowerText === '/สรุปวันนี้' || $lowerText === '/whosout') {
        // ตรวจสอบว่าผู้ใช้ลงทะเบียนแล้ว
        $stmt = $pdo->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$userId]);
        if (!$stmt->fetch()) {
            $telegramService->sendMessage($chatId, "❌ กรุณาลงทะเบียนก่อนใช้งาน พิมพ์ /register");
            exit;
        }

        $hrHandler = new HrSummaryHandler($telegramService, $chatId, $userId);
        $hrHandler->showTodaysAbsences();
        clearUserStateWebhook($userId, $chatId);
    } elseif ($lowerText === '/start') {
        log_message("Processing /start command for user {$userId}");
        $stmt = $pdo->prepare("SELECT first_name, last_name FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$userId]);
        $employee = $stmt->fetch();

        if ($employee) {
            $employeeName = $employee['first_name'].' '.$employee['last_name'];
            $responseText = "สวัสดีครับคุณ {$employeeName}! บอทน้องลา (Leave & WFH Bot) พร้อมให้บริการแล้วครับ\nพิมพ์ /help เพื่อดูคำสั่งที่ใช้ได้";
            log_message("Found registered employee: {$employeeName}");
        } else {
            $responseText = "สวัสดีครับคุณ {$firstName}! บอทน้องลา (Leave & WFH Bot) พร้อมให้บริการแล้วครับ\n\n";
            if (ALLOW_SELF_REGISTRATION) {
                $responseText .= "🎯 คุณยังไม่ได้ลงทะเบียนในระบบ กรุณาพิมพ์ /register เพื่อเริ่มการลงทะเบียน\n\n";
            } else {
                $responseText .= "❌ การลงทะเบียนด้วยตัวเองถูกปิดใช้งาน กรุณาติดต่อผู้ดูแลระบบ\n\n";
            }
            $responseText .= "พิมพ์ /help เพื่อดูคำสั่งที่ใช้ได้";
            log_message("New user, self registration: ".(ALLOW_SELF_REGISTRATION ? "ENABLED" : "DISABLED"));
        }
        $result = $telegramService->sendMessage($chatId, $responseText);
        log_message("Start response sent to user {$userId}. Result: ".json_encode($result));
        clearUserStateWebhook($userId, $chatId);
    } elseif ($lowerText === '/register') {
        log_message("Processing /register command for user {$userId}");
        $result = $selfRegistrationHandler->startRegistration($chatId, $userId);
        log_message("Registration start result for user {$userId}: ".json_encode($result));
        clearUserStateWebhook($userId, $chatId);
    } elseif ($lowerText === '/ลงทะเบียน') {
        $selfRegistrationHandler->startRegistration($chatId, $userId);
        clearUserStateWebhook($userId, $chatId);
    } elseif ($lowerText === '/สถานะ' || $lowerText === '/status') {
        $stmt = $pdo->prepare("SELECT employee_code, first_name, last_name, email FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$userId]);
        $employee = $stmt->fetch();

        if ($employee) {
            $responseText = "✅ *สถานะการลงทะเบียน*\n\n".
                "👤 ชื่อ: {$employee['first_name']} {$employee['last_name']}\n".
                "📧 อีเมล: {$employee['email']}\n";
            if ($employee['employee_code']) {
                $responseText .= "🏷️ รหัสพนักงาน: {$employee['employee_code']}\n";
            }
            $responseText .= "\n✅ ลงทะเบียนแล้ว สามารถใช้งานได้";
        } else {
            if (ALLOW_SELF_REGISTRATION) {
                $responseText = "❌ คุณยังไม่ได้ลงทะเบียนในระบบ\nพิมพ์ /register เพื่อเริ่มการลงทะเบียน";
            } else {
                $responseText = "❌ คุณยังไม่ได้ลงทะเบียนในระบบ\nการลงทะเบียนด้วยตัวเองถูกปิดใช้งาน กรุณาติดต่อผู้ดูแลระบบ";
            }
        }
        $telegramService->sendMessage($chatId, $responseText, null, 'Markdown');
        clearUserStateWebhook($userId, $chatId);
    } elseif ($lowerText === '/help') {
        $stmt = $pdo->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
        $stmt->execute([$userId]);
        $isRegistered = $stmt->fetch();

        if ($isRegistered) {
            $responseText = "คำสั่งที่น้องลาเข้าใจตอนนี้:\n".
                "/start - เริ่มการสนทนา\n".
                "/help - แสดงข้อความช่วยเหลือนี้\n".
                "/สถานะ (หรือ /status) - ตรวจสอบสถานะการลงทะเบียน\n".
                "/ลา (หรือ /leave) - เริ่มขั้นตอนการขอลา\n".
                "/wfh - เริ่มขั้นตอนการขอ Work From Home\n".
                "/เช็คยอด (หรือ /balance) - ตรวจสอบยอดวันลาคงเหลือ\n".
                "/ยกเลิกคำขอ (หรือ /cancelrequest) - ยกเลิกคำขอที่รออนุมัติ\n".
                "/สรุปวันนี้ (หรือ /whosout) - (HR) สรุปผู้ที่ไม่อยู่ในออฟฟิศวันนี้\n".
                "/cancel - ยกเลิกการดำเนินการปัจจุบัน";
        } else {
            $responseText = "คำสั่งที่น้องลาเข้าใจตอนนี้:\n".
                "/start - เริ่มการสนทนา\n".
                "/help - แสดงข้อความช่วยเหลือนี้\n";
            if (ALLOW_SELF_REGISTRATION) {
                $responseText .= "/register - ลงทะเบียนเข้าใช้งานระบบ\n";
            }
            $responseText .= "/สถานะ (หรือ /status) - ตรวจสอบสถานะการลงทะเบียน\n\n";
            if (ALLOW_SELF_REGISTRATION) {
                $responseText .= "⚠️ คุณต้องลงทะเบียนก่อนเพื่อใช้งานคำสั่งอื่นๆ";
            } else {
                $responseText .= "⚠️ การลงทะเบียนด้วยตัวเองถูกปิดใช้งาน กรุณาติดต่อผู้ดูแลระบบ";
            }
        }
        $telegramService->sendMessage($chatId, $responseText);
        clearUserStateWebhook($userId, $chatId);
    } elseif ($lowerText === '/cancel') {
        clearUserStateWebhook($userId, $chatId);
        $selfRegistrationHandler->cancelRegistration($chatId, $userId);
    } else if (str_starts_with($lowerText, '/') && !$userState) {
        $responseText = "ขออภัยครับคุณ {$firstName}, น้องลายังไม่เข้าใจคำสั่ง: '{$text}'. ลองพิมพ์ /help ดูนะครับ";
        $telegramService->sendMessage($chatId, $responseText);
    }

} elseif (isset($update['callback_query'])) {
    $callbackQuery = $update['callback_query'];
    $chatId = $callbackQuery['message']['chat']['id'];
    $userId = $callbackQuery['from']['id'];
    $callbackData = $callbackQuery['data'] ?? '';
    $callbackQueryId = $callbackQuery['id'];
    $originalMessageId = $callbackQuery['message']['message_id'];

    $telegramService->answerCallbackQuery($callbackQueryId);

    // ตรวจสอบสิทธิ์การเข้าใช้งาน
    if (!$selfRegistrationHandler->canUserAccessBot($userId)) {
        $telegramService->editMessageText($chatId, $originalMessageId,
            "❌ ขออภัย คุณยังไม่ได้รับอนุญาตให้ใช้บอทนี้"
        );
        exit;
    }

    // ตรวจสอบว่าผู้ใช้ลงทะเบียนแล้ว
    $stmt = $pdo->prepare("SELECT id FROM employees WHERE chat_user_id = ?");
    $stmt->execute([$userId]);
    if (!$stmt->fetch()) {
        $telegramService->editMessageText($chatId, $originalMessageId,
            "❌ กรุณาลงทะเบียนก่อนใช้งาน พิมพ์ /register"
        );
        exit;
    }

    $userState = getUserState($userId, $chatId);

    $currentLeaveAppMode = 'leave';
    if ($userState) {
        if (str_starts_with($userState, 'wfh_')) {
            $currentLeaveAppMode = 'wfh';
        }
    }

    if (str_starts_with($callbackData, 'req_type_')) {
        $leaveAppHandler = new LeaveApplicationHandler($telegramService, $chatId, $userId, $currentLeaveAppMode);
        $leaveAppHandler->handleTypeSelection($callbackData);
    } elseif (str_starts_with($callbackData, 'req_action_')) {
        $leaveAppHandler = new LeaveApplicationHandler($telegramService, $chatId, $userId, $currentLeaveAppMode);
        if ($userState && (str_starts_with($userState, 'la_confirm_') || str_starts_with($userState, 'wfh_confirm_'))) {
            $leaveAppHandler->handleConfirmationAction($userState, $callbackData);
        } else {
            $telegramService->editMessageText($chatId, $originalMessageId, "การดำเนินการนี้อาจหมดอายุหรือไม่ถูกต้องแล้วค่ะ (ไม่พบ state)");
            if ($userState) {
                clearUserStateWebhook($userId, $chatId);
            }
        }
    } elseif (str_starts_with($callbackData, 'cancelReq_select_')) {
        $cancelHandler = new CancelRequestHandler($telegramService, $chatId, $userId);
        $cancelHandler->handleCancellationSelection($callbackData);
    } elseif (str_starts_with($callbackData, 'cancelReq_confirm_') || $callbackData === 'cancelReq_abort') {
        $cancelHandler = new CancelRequestHandler($telegramService, $chatId, $userId);
        if ($userState && str_starts_with($userState, 'cancelReq_confirm_')) {
            $cancelHandler->handleCancellationConfirmation($userState, $callbackData);
        } else if ($callbackData === 'cancelReq_abort' && $userState && str_starts_with($userState, 'cancelReq_confirm_')) {
            // Ensure abort also needs the confirm state
            $cancelHandler->handleCancellationConfirmation($userState, $callbackData);
        } else {
            $telegramService->editMessageText($chatId, $originalMessageId, "การดำเนินการยกเลิกนี้อาจหมดอายุหรือไม่ถูกต้องแล้วค่ะ (ไม่พบ state)");
            if ($userState) {
                clearUserStateWebhook($userId, $chatId);
            }
        }
    } elseif (str_starts_with($callbackData, 'mgrAction_')) {
        $managerHandler = new ManagerActionHandler($telegramService, $chatId, $userId);
        $managerHandler->handleApprovalAction($callbackData, $originalMessageId);
    } else {
        log_message("Unhandled callback_data: {$callbackData} from user {$userId} in chat {$chatId}");
    }
} else {
    log_message("Received an update type that is not a message or callback_query: ".json_encode(array_keys($update)));
}

http_response_code(200);
exit;