<?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"
);
}
}