Update Saqel Platform: 2026-09-08 13:43:36
This commit is contained in:
@@ -2,360 +2,155 @@
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Core\Request;
|
||||
use App\Core\Response;
|
||||
use App\Core\Database;
|
||||
use App\Core\Security;
|
||||
use App\Core\Validator;
|
||||
|
||||
class ChatController
|
||||
{
|
||||
/**
|
||||
* Zero Mock Policy - Only real database users are queried
|
||||
*/
|
||||
private static function ensureSeedUsers(): void
|
||||
{
|
||||
// No automatic demo users or students insertion
|
||||
}
|
||||
|
||||
/**
|
||||
* List all active conversations and available contacts (Student or Teacher)
|
||||
* GET /api/chat/conversations
|
||||
*/
|
||||
public function getConversations(Request $request, Response $response): void
|
||||
{
|
||||
self::ensureSeedUsers();
|
||||
$identityId = (int)$request->identity_id;
|
||||
$contacts = $request->role === 'teacher'
|
||||
? Database::select(
|
||||
"SELECT DISTINCT ai.id AS user_id, s.uuid AS user_uuid, s.full_name, 'student' AS role, s.grade_level AS specialization
|
||||
FROM course_access_passes cap
|
||||
JOIN courses c ON c.id = cap.course_id
|
||||
JOIN students s ON s.id = cap.student_id
|
||||
JOIN auth_identities ai ON ai.id = s.identity_id AND ai.status = 'active'
|
||||
WHERE c.teacher_id = ? AND cap.is_active = 1",
|
||||
[$request->user_id]
|
||||
)
|
||||
: Database::select(
|
||||
"SELECT DISTINCT ai.id AS user_id, t.uuid AS user_uuid, t.full_name, 'teacher' AS role, t.specialization
|
||||
FROM course_access_passes cap
|
||||
JOIN courses c ON c.id = cap.course_id
|
||||
JOIN teachers t ON t.id = c.teacher_id
|
||||
JOIN auth_identities ai ON ai.id = t.identity_id AND ai.status = 'active'
|
||||
WHERE cap.student_id = ? AND cap.is_active = 1",
|
||||
[$request->user_id]
|
||||
);
|
||||
|
||||
$userId = (int)$request->user_id;
|
||||
|
||||
// 1. Fetch available teachers and students
|
||||
$users = [];
|
||||
try {
|
||||
$teachers = Database::select("SELECT id as user_id, uuid as user_uuid, full_name as encrypted_name, 'teacher' as user_role, specialization FROM teachers WHERE id != ? LIMIT 50", [$userId]);
|
||||
$students = Database::select("SELECT id as user_id, uuid as user_uuid, full_name as encrypted_name, 'student' as user_role, 'طالب' as specialization FROM students WHERE id != ? LIMIT 50", [$userId]);
|
||||
$users = array_merge($teachers, $students);
|
||||
} catch (\Throwable $e) {
|
||||
error_log("Users select in getConversations error: " . $e->getMessage());
|
||||
foreach ($contacts as &$contact) {
|
||||
$other = (int)$contact['user_id'];
|
||||
$last = Database::selectOne(
|
||||
"SELECT message, message_type, created_at FROM chat_messages
|
||||
WHERE (sender_identity_id = ? AND receiver_identity_id = ?) OR (sender_identity_id = ? AND receiver_identity_id = ?)
|
||||
ORDER BY id DESC LIMIT 1",
|
||||
[$identityId, $other, $other, $identityId]
|
||||
);
|
||||
$unread = Database::selectOne(
|
||||
"SELECT COUNT(*) AS count_value FROM chat_messages WHERE sender_identity_id = ? AND receiver_identity_id = ? AND is_read = 0",
|
||||
[$other, $identityId]
|
||||
);
|
||||
$contact['last_message'] = $last['message'] ?? '';
|
||||
$contact['last_message_type'] = $last['message_type'] ?? '';
|
||||
$contact['last_message_at'] = $last['created_at'] ?? null;
|
||||
$contact['unread_count'] = (int)($unread['count_value'] ?? 0);
|
||||
}
|
||||
|
||||
$conversations = [];
|
||||
foreach ($users as $row) {
|
||||
$otherId = (int)$row['user_id'];
|
||||
|
||||
// Get latest message between $userId and $otherId
|
||||
$lastMsg = null;
|
||||
$unreadCount = 0;
|
||||
try {
|
||||
$lastMsg = Database::selectOne("
|
||||
SELECT message, message_type, created_at
|
||||
FROM chat_messages
|
||||
WHERE (sender_id = ? AND receiver_id = ?) OR (sender_id = ? AND receiver_id = ?)
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
", [$userId, $otherId, $otherId, $userId]);
|
||||
|
||||
$unreadRow = Database::selectOne("
|
||||
SELECT COUNT(*) as cnt
|
||||
FROM chat_messages
|
||||
WHERE sender_id = ? AND receiver_id = ? AND is_read = 0
|
||||
", [$otherId, $userId]);
|
||||
$unreadCount = (int)($unreadRow['cnt'] ?? 0);
|
||||
} catch (\Throwable $e) {
|
||||
// Ignore per-user message query issue if any
|
||||
}
|
||||
|
||||
$rawName = (string)($row['encrypted_name'] ?? '');
|
||||
$decryptedName = $rawName;
|
||||
try {
|
||||
$dec = Security::decrypt($rawName);
|
||||
if (!empty($dec)) {
|
||||
$decryptedName = $dec;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$decryptedName = $rawName;
|
||||
}
|
||||
|
||||
$conversations[] = [
|
||||
'user_id' => $otherId,
|
||||
'user_uuid' => $row['user_uuid'] ?? '',
|
||||
'full_name' => $decryptedName ?: ($row['user_role'] === 'teacher' ? 'الأستاذ حمزة الغويري' : 'طالب صَقِل'),
|
||||
'role' => $row['user_role'] ?? 'student',
|
||||
'specialization' => $row['specialization'] ?? ($row['user_role'] === 'teacher' ? 'الرياضيات العلمي' : null),
|
||||
'last_message' => $lastMsg['message'] ?? ($row['user_role'] === 'teacher' ? 'متاح للإجابة عن استفساراتك' : 'طالب جديد — اضغط لبدء المحادثة'),
|
||||
'last_message_type' => $lastMsg['message_type'] ?? 'text',
|
||||
'last_message_at' => $lastMsg['created_at'] ?? null,
|
||||
'unread_count' => $unreadCount,
|
||||
];
|
||||
}
|
||||
|
||||
// Sort: users with active messages first, then by last_message_at DESC
|
||||
usort($conversations, function ($a, $b) {
|
||||
if ($a['last_message_at'] && !$b['last_message_at']) return -1;
|
||||
if (!$a['last_message_at'] && $b['last_message_at']) return 1;
|
||||
if ($a['last_message_at'] && $b['last_message_at']) {
|
||||
return strcmp($b['last_message_at'], $a['last_message_at']);
|
||||
}
|
||||
return $b['user_id'] <=> $a['user_id'];
|
||||
});
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'data' => $conversations
|
||||
]);
|
||||
unset($contact);
|
||||
$response->json(['status' => 'success', 'data' => $contacts]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get chat message history between authenticated user and another user
|
||||
* GET /api/chat/messages?other_user_id=123&course_id=456
|
||||
*/
|
||||
public function getMessages(Request $request, Response $response): void
|
||||
{
|
||||
$userId = $request->user_id;
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
$otherUserId = (int)($queryParams['other_user_id'] ?? $queryParams['user_id'] ?? 0);
|
||||
$courseId = !empty($queryParams['course_id']) ? (int)$queryParams['course_id'] : null;
|
||||
$limit = min(100, max(1, (int)($queryParams['limit'] ?? 50)));
|
||||
|
||||
if (!$otherUserId) {
|
||||
$response->status(400)->json([
|
||||
'status' => 'error',
|
||||
'message' => 'معرف الطرف الآخر (other_user_id) مطلوب'
|
||||
]);
|
||||
$me = (int)$request->identity_id;
|
||||
$other = (int)($request->getQuery('other_user_id', $request->getQuery('user_id', 0)));
|
||||
if (!$other || !$this->isAllowedContact($request, $other)) {
|
||||
$response->status(403)->json(['status' => 'error', 'message' => 'المحادثة غير مصرح بها']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch other user info
|
||||
$otherUser = Database::selectOne("SELECT id, uuid, full_name, role FROM users WHERE id = ? LIMIT 1", [$otherUserId]);
|
||||
if (!$otherUser) {
|
||||
$response->status(404)->json(['status' => 'error', 'message' => 'المستخدم الآخر غير موجود']);
|
||||
return;
|
||||
}
|
||||
|
||||
$rawOtherName = (string)($otherUser['full_name'] ?? '');
|
||||
$otherUserName = Security::decrypt($rawOtherName) ?: $rawOtherName;
|
||||
|
||||
// Fetch messages bidirectional
|
||||
$sql = "
|
||||
SELECT id, uuid, sender_id, receiver_id, course_id, lesson_id, message, message_type, media_url, is_read, read_at, created_at
|
||||
FROM chat_messages
|
||||
WHERE ((sender_id = ? AND receiver_id = ?) OR (sender_id = ? AND receiver_id = ?))
|
||||
";
|
||||
$params = [$userId, $otherUserId, $otherUserId, $userId];
|
||||
|
||||
if ($courseId) {
|
||||
$sql .= " AND (course_id = ? OR course_id IS NULL)";
|
||||
$params[] = $courseId;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY created_at ASC LIMIT ?";
|
||||
$params[] = $limit;
|
||||
|
||||
$messages = Database::select($sql, $params);
|
||||
|
||||
// Mark unread messages sent by other user to me as read
|
||||
Database::query(
|
||||
"UPDATE chat_messages SET is_read = 1, read_at = NOW() WHERE sender_id = ? AND receiver_id = ? AND is_read = 0",
|
||||
[$otherUserId, $userId]
|
||||
$limit = min(100, max(1, (int)$request->getQuery('limit', 50)));
|
||||
$messages = Database::select(
|
||||
"SELECT id, uuid, sender_identity_id AS sender_id, receiver_identity_id AS receiver_id,
|
||||
course_id, lesson_id, message, message_type, media_url, is_read, read_at, created_at
|
||||
FROM chat_messages
|
||||
WHERE (sender_identity_id = ? AND receiver_identity_id = ?) OR (sender_identity_id = ? AND receiver_identity_id = ?)
|
||||
ORDER BY created_at ASC LIMIT ?",
|
||||
[$me, $other, $other, $me, $limit]
|
||||
);
|
||||
|
||||
$formattedMessages = array_map(function ($msg) use ($userId) {
|
||||
return [
|
||||
'id' => (int)$msg['id'],
|
||||
'uuid' => $msg['uuid'],
|
||||
'is_mine' => ((int)$msg['sender_id'] === (int)$userId),
|
||||
'sender_id' => (int)$msg['sender_id'],
|
||||
'receiver_id' => (int)$msg['receiver_id'],
|
||||
'course_id' => $msg['course_id'] ? (int)$msg['course_id'] : null,
|
||||
'lesson_id' => $msg['lesson_id'] ? (int)$msg['lesson_id'] : null,
|
||||
'message' => $msg['message'],
|
||||
'message_type' => $msg['message_type'],
|
||||
'media_url' => $msg['media_url'],
|
||||
'is_read' => (bool)$msg['is_read'],
|
||||
'read_at' => $msg['read_at'],
|
||||
'created_at' => $msg['created_at'],
|
||||
];
|
||||
}, $messages);
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'other_user' => [
|
||||
'id' => (int)$otherUser['id'],
|
||||
'uuid' => $otherUser['uuid'],
|
||||
'full_name' => $otherUserName,
|
||||
'role' => $otherUser['role'],
|
||||
],
|
||||
'messages' => $formattedMessages
|
||||
]
|
||||
]);
|
||||
foreach ($messages as &$message) $message['is_mine'] = (int)$message['sender_id'] === $me;
|
||||
unset($message);
|
||||
Database::query("UPDATE chat_messages SET is_read = 1, read_at = NOW() WHERE sender_identity_id = ? AND receiver_identity_id = ? AND is_read = 0", [$other, $me]);
|
||||
$response->json(['status' => 'success', 'data' => ['other_user' => $this->identityProfile($other), 'messages' => $messages]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a new chat message (Text, Voice Note, Image, File)
|
||||
* POST /api/chat/messages
|
||||
*/
|
||||
public function sendMessage(Request $request, Response $response): void
|
||||
{
|
||||
$userId = $request->user_id;
|
||||
$body = $request->getBody();
|
||||
|
||||
$validator = new Validator();
|
||||
$isValid = $validator->validate($body, [
|
||||
'receiver_id' => 'required',
|
||||
'message' => 'required'
|
||||
]);
|
||||
|
||||
if (!$isValid) {
|
||||
$response->status(400)->json([
|
||||
'status' => 'error',
|
||||
'message' => 'معرف المستقبل ونص الرسالة مطلوبان',
|
||||
'errors' => $validator->getErrors()
|
||||
]);
|
||||
$receiver = (int)($body['receiver_id'] ?? 0);
|
||||
$message = trim((string)($body['message'] ?? ''));
|
||||
$type = (string)($body['message_type'] ?? 'text');
|
||||
if (!$receiver || $message === '' || !$this->isAllowedContact($request, $receiver)) {
|
||||
$response->status(403)->json(['status' => 'error', 'message' => 'المستلم أو الرسالة غير صالحين لهذه المحادثة']);
|
||||
return;
|
||||
}
|
||||
|
||||
$receiverId = (int)$body['receiver_id'];
|
||||
$messageText = trim((string)$body['message']);
|
||||
$messageType = (string)($body['message_type'] ?? 'text');
|
||||
$mediaUrl = !empty($body['media_url']) ? trim((string)$body['media_url']) : null;
|
||||
$courseId = !empty($body['course_id']) ? (int)$body['course_id'] : null;
|
||||
$lessonId = !empty($body['lesson_id']) ? (int)$body['lesson_id'] : null;
|
||||
|
||||
if (!in_array($messageType, ['text', 'voice', 'image', 'file'], true)) {
|
||||
$messageType = 'text';
|
||||
}
|
||||
|
||||
// Validate receiver exists, or fallback to counterpart role automatically
|
||||
$receiver = Database::selectOne("SELECT id, role FROM users WHERE id = ? LIMIT 1", [$receiverId]);
|
||||
if (!$receiver) {
|
||||
$targetRole = ($request->role === 'teacher') ? 'student' : 'teacher';
|
||||
$fallbackReceiver = Database::selectOne("SELECT id, role FROM users WHERE role = ? ORDER BY id ASC LIMIT 1", [$targetRole]);
|
||||
if (!$fallbackReceiver) {
|
||||
self::ensureSeedUsers();
|
||||
$fallbackReceiver = Database::selectOne("SELECT id, role FROM users WHERE role = ? ORDER BY id ASC LIMIT 1", [$targetRole]);
|
||||
}
|
||||
if ($fallbackReceiver) {
|
||||
$receiverId = (int)$fallbackReceiver['id'];
|
||||
$receiver = $fallbackReceiver;
|
||||
} else {
|
||||
$response->status(404)->json([
|
||||
'status' => 'error',
|
||||
'message' => 'المستخدم المستلم غير موجود'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
if (!in_array($type, ['text', 'voice', 'image', 'file'], true)) $type = 'text';
|
||||
$uuid = $this->uuid();
|
||||
$id = Database::insert(
|
||||
"INSERT INTO chat_messages (uuid, sender_identity_id, receiver_identity_id, course_id, lesson_id, message, message_type, media_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
[$uuid, $request->identity_id, $receiver, !empty($body['course_id']) ? (int)$body['course_id'] : null, !empty($body['lesson_id']) ? (int)$body['lesson_id'] : null, $message, $type, $body['media_url'] ?? null]
|
||||
);
|
||||
|
||||
$messageId = Database::insert(
|
||||
"INSERT INTO chat_messages (uuid, sender_id, receiver_id, course_id, lesson_id, message, message_type, media_url, is_read)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)",
|
||||
[$uuid, $userId, $receiverId, $courseId, $lessonId, $messageText, $messageType, $mediaUrl]
|
||||
);
|
||||
|
||||
$msgPayload = [
|
||||
'id' => $messageId,
|
||||
'uuid' => $uuid,
|
||||
'sender_id' => $userId,
|
||||
'receiver_id' => $receiverId,
|
||||
'is_mine' => false,
|
||||
'course_id' => $courseId,
|
||||
'lesson_id' => $lessonId,
|
||||
'message' => $messageText,
|
||||
'message_type' => $messageType,
|
||||
'media_url' => $mediaUrl,
|
||||
'is_read' => false,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
// Push directly to Workerman WebSocket server for instant 0ms live broadcast to receiver
|
||||
self::pushToWorkerman('push_chat', $receiverId, $msgPayload);
|
||||
|
||||
$response->status(201)->json([
|
||||
'status' => 'success',
|
||||
'message' => 'تم إرسال الرسالة بنجاح',
|
||||
'data' => array_merge($msgPayload, ['is_mine' => true])
|
||||
]);
|
||||
$payload = ['id' => $id, 'uuid' => $uuid, 'sender_id' => $request->identity_id, 'receiver_id' => $receiver, 'message' => $message, 'message_type' => $type, 'media_url' => $body['media_url'] ?? null, 'is_read' => false, 'is_mine' => true, 'created_at' => date('Y-m-d H:i:s')];
|
||||
self::pushToWorkerman('push_chat', $receiver, $payload);
|
||||
$response->status(201)->json(['status' => 'success', 'data' => $payload]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push internal event to Workerman's internal HTTP server (port 4241)
|
||||
*/
|
||||
public static function pushToWorkerman(string $action, int $targetUserId, array $payload): bool
|
||||
{
|
||||
try {
|
||||
$ch = curl_init('http://127.0.0.1:4241');
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
|
||||
'action' => $action,
|
||||
'target_user_id' => $targetUserId,
|
||||
'payload' => json_encode($payload, JSON_UNESCAPED_UNICODE)
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 400); // Fast non-blocking timeout
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all messages in a conversation as read
|
||||
* POST /api/chat/read
|
||||
*/
|
||||
public function markAsRead(Request $request, Response $response): void
|
||||
{
|
||||
$userId = $request->user_id;
|
||||
$body = $request->getBody();
|
||||
|
||||
$senderId = (int)($body['sender_id'] ?? 0);
|
||||
if (!$senderId) {
|
||||
$response->status(400)->json([
|
||||
'status' => 'error',
|
||||
'message' => 'معرف المرسل مطلوب'
|
||||
]);
|
||||
$sender = (int)($request->getBody()['sender_id'] ?? 0);
|
||||
if (!$sender || !$this->isAllowedContact($request, $sender)) {
|
||||
$response->status(403)->json(['status' => 'error', 'message' => 'المحادثة غير مصرح بها']);
|
||||
return;
|
||||
}
|
||||
|
||||
Database::query(
|
||||
"UPDATE chat_messages SET is_read = 1, read_at = NOW() WHERE sender_id = ? AND receiver_id = ? AND is_read = 0",
|
||||
[$senderId, $userId]
|
||||
);
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'message' => 'تم تحديث حالة القراءة'
|
||||
]);
|
||||
Database::query("UPDATE chat_messages SET is_read = 1, read_at = NOW() WHERE sender_identity_id = ? AND receiver_identity_id = ? AND is_read = 0", [$sender, $request->identity_id]);
|
||||
$response->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total unread count for badge notification
|
||||
* GET /api/chat/unread-count
|
||||
*/
|
||||
public function getUnreadCount(Request $request, Response $response): void
|
||||
{
|
||||
$userId = $request->user_id;
|
||||
$row = Database::selectOne("SELECT COUNT(*) AS unread_total FROM chat_messages WHERE receiver_identity_id = ? AND is_read = 0", [$request->identity_id]);
|
||||
$response->json(['status' => 'success', 'data' => ['unread_count' => (int)($row['unread_total'] ?? 0)]]);
|
||||
}
|
||||
|
||||
$row = Database::selectOne(
|
||||
"SELECT COUNT(*) as unread_total FROM chat_messages WHERE receiver_id = ? AND is_read = 0",
|
||||
[$userId]
|
||||
private function isAllowedContact(Request $request, int $otherIdentityId): bool
|
||||
{
|
||||
if ($request->role === 'teacher') {
|
||||
return (bool)Database::selectOne(
|
||||
"SELECT 1 FROM course_access_passes cap JOIN courses c ON c.id = cap.course_id JOIN students s ON s.id = cap.student_id
|
||||
WHERE c.teacher_id = ? AND s.identity_id = ? AND cap.is_active = 1 LIMIT 1",
|
||||
[$request->user_id, $otherIdentityId]
|
||||
);
|
||||
}
|
||||
return (bool)Database::selectOne(
|
||||
"SELECT 1 FROM course_access_passes cap JOIN courses c ON c.id = cap.course_id JOIN teachers t ON t.id = c.teacher_id
|
||||
WHERE cap.student_id = ? AND t.identity_id = ? AND cap.is_active = 1 LIMIT 1",
|
||||
[$request->user_id, $otherIdentityId]
|
||||
);
|
||||
}
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'unread_count' => (int)($row['unread_total'] ?? 0)
|
||||
]
|
||||
]);
|
||||
private function identityProfile(int $identityId): array
|
||||
{
|
||||
$profile = Database::selectOne("SELECT id, uuid, full_name, 'student' AS role FROM students WHERE identity_id = ? LIMIT 1", [$identityId]);
|
||||
if (!$profile) $profile = Database::selectOne("SELECT id, uuid, full_name, 'teacher' AS role FROM teachers WHERE identity_id = ? LIMIT 1", [$identityId]);
|
||||
return $profile ?: [];
|
||||
}
|
||||
|
||||
public static function pushToWorkerman(string $action, int $targetIdentityId, array $payload): bool
|
||||
{
|
||||
$ch = curl_init('http://127.0.0.1:4241');
|
||||
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query(['action' => $action, 'target_user_id' => $targetIdentityId, 'payload' => json_encode($payload, JSON_UNESCAPED_UNICODE)]), CURLOPT_TIMEOUT_MS => 400, CURLOPT_RETURNTRANSFER => true]);
|
||||
$ok = curl_exec($ch) !== false;
|
||||
curl_close($ch);
|
||||
return $ok;
|
||||
}
|
||||
|
||||
private function uuid(): string
|
||||
{
|
||||
$data = random_bytes(16);
|
||||
$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
|
||||
$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user