![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/private_html/api/ |
<?php
/**
* Messages API Endpoint
* Handles all messaging operations: send, get conversations, get messages, mark as read
*/
header('Content-Type: application/json');
session_start();
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../includes/security.php';
// Check authentication
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'message' => 'Authentication required']);
exit;
}
$user_id = $_SESSION['user_id'];
$pdo = getDBConnection();
if (!$pdo) {
echo json_encode(['success' => false, 'message' => 'Database connection failed']);
exit;
}
// Update last_activity timestamp for online tracking
// This ensures users appear online when actively using the messaging system
try {
$pdo->exec("ALTER TABLE users ADD COLUMN IF NOT EXISTS last_activity TIMESTAMP NULL DEFAULT NULL");
$updateStmt = $pdo->prepare("UPDATE users SET last_activity = NOW() WHERE id = ?");
$updateStmt->execute([$user_id]);
} catch (Exception $e) {
// Ignore errors - column might not exist yet or update might fail
error_log("Error updating last_activity in messages API: " . $e->getMessage());
}
// Ensure user_messages table exists
try {
$pdo->exec("
CREATE TABLE IF NOT EXISTS user_messages (
id INT AUTO_INCREMENT PRIMARY KEY,
sender_id INT NOT NULL,
receiver_id INT NOT NULL,
message TEXT NOT NULL,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (receiver_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_sender (sender_id),
INDEX idx_receiver (receiver_id),
INDEX idx_created (created_at),
INDEX idx_read (is_read)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
");
} catch (PDOException $e) {
error_log("Error creating user_messages table: " . $e->getMessage());
}
$method = $_SERVER['REQUEST_METHOD'];
$action = $_GET['action'] ?? $_POST['action'] ?? '';
try {
switch ($action) {
case 'get_conversations':
// Get all conversations for the current user
$stmt = $pdo->prepare("
SELECT
CASE
WHEN sender_id = ? THEN receiver_id
ELSE sender_id
END as other_user_id,
u.name as other_user_name,
u.email as other_user_email,
up.profile_image,
MAX(um.created_at) as last_message_time,
MAX(um.message) as last_message,
SUM(CASE WHEN um.receiver_id = ? AND um.is_read = FALSE THEN 1 ELSE 0 END) as unread_count,
MAX(CASE WHEN um.sender_id = ? THEN 1 ELSE 0 END) as is_sender
FROM user_messages um
JOIN users u ON (
CASE
WHEN um.sender_id = ? THEN u.id = um.receiver_id
ELSE u.id = um.sender_id
END
)
LEFT JOIN user_profiles up ON u.id = up.user_id
WHERE um.sender_id = ? OR um.receiver_id = ?
GROUP BY other_user_id, u.name, u.email, up.profile_image
ORDER BY last_message_time DESC
");
$stmt->execute([$user_id, $user_id, $user_id, $user_id, $user_id, $user_id]);
$conversations = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Format conversations
foreach ($conversations as &$conv) {
$conv['unread_count'] = (int)$conv['unread_count'];
$conv['is_sender'] = (bool)$conv['is_sender'];
$conv['last_message'] = html_entity_decode($conv['last_message'] ?? '', ENT_QUOTES | ENT_HTML5, 'UTF-8');
$conv['last_message'] = mb_substr($conv['last_message'], 0, 100);
$conv['avatar'] = formatProfileImage($conv['profile_image'], $conv['other_user_name']);
}
echo json_encode(['success' => true, 'conversations' => $conversations]);
break;
case 'get_messages':
// Get messages between current user and another user
$other_user_id = $_GET['user_id'] ?? null;
if (!$other_user_id || !is_numeric($other_user_id)) {
echo json_encode(['success' => false, 'message' => 'Invalid user ID']);
exit;
}
$other_user_id = (int)$other_user_id;
// Get messages
$stmt = $pdo->prepare("
SELECT
um.*,
u.name as sender_name,
up.profile_image as sender_image
FROM user_messages um
JOIN users u ON um.sender_id = u.id
LEFT JOIN user_profiles up ON u.id = up.user_id
WHERE (um.sender_id = ? AND um.receiver_id = ?)
OR (um.sender_id = ? AND um.receiver_id = ?)
ORDER BY um.created_at ASC
");
$stmt->execute([$user_id, $other_user_id, $other_user_id, $user_id]);
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Mark messages as read
$stmt = $pdo->prepare("
UPDATE user_messages
SET is_read = TRUE
WHERE sender_id = ? AND receiver_id = ? AND is_read = FALSE
");
$stmt->execute([$other_user_id, $user_id]);
// Get other user info with online status
// Calculate online status directly in SQL for better timezone handling
$stmt = $pdo->prepare("
SELECT
u.id,
u.name,
u.email,
up.profile_image,
up.bio,
u.last_activity,
CASE
WHEN u.last_activity IS NOT NULL
AND u.last_activity >= DATE_SUB(NOW(), INTERVAL 15 MINUTE)
THEN 1
ELSE 0
END as is_online
FROM users u
LEFT JOIN user_profiles up ON u.id = up.user_id
WHERE u.id = ?
");
$stmt->execute([$other_user_id]);
$other_user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$other_user) {
echo json_encode(['success' => false, 'message' => 'User not found']);
exit;
}
// Use the is_online value calculated by MySQL
$is_online = (bool)($other_user['is_online'] ?? false);
// Format messages
foreach ($messages as &$msg) {
$msg['message'] = html_entity_decode($msg['message'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
$msg['is_sender'] = (int)$msg['sender_id'] === $user_id;
$msg['is_read'] = (bool)$msg['is_read'];
$msg['read_at'] = $msg['is_read'] ? $msg['updated_at'] : null;
$msg['sender_avatar'] = formatProfileImage($msg['sender_image'], $msg['sender_name']);
}
echo json_encode([
'success' => true,
'messages' => $messages,
'other_user' => [
'id' => $other_user['id'],
'name' => $other_user['name'],
'avatar' => formatProfileImage($other_user['profile_image'], $other_user['name']),
'is_online' => $is_online
]
]);
break;
case 'send_message':
// Send a new message
$receiver_id = $_POST['receiver_id'] ?? null;
$message = trim($_POST['message'] ?? '');
if (!$receiver_id || !is_numeric($receiver_id)) {
echo json_encode(['success' => false, 'message' => 'Invalid receiver ID']);
exit;
}
if (empty($message)) {
echo json_encode(['success' => false, 'message' => 'Message cannot be empty']);
exit;
}
$receiver_id = (int)$receiver_id;
if ($receiver_id === $user_id) {
echo json_encode(['success' => false, 'message' => 'You cannot send messages to yourself']);
exit;
}
// Verify receiver exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE id = ?");
$stmt->execute([$receiver_id]);
if (!$stmt->fetch()) {
echo json_encode(['success' => false, 'message' => 'User not found']);
exit;
}
// Normalize message length while keeping raw text (escaping happens on output)
$message = mb_substr($message, 0, 5000);
// Insert message
$stmt = $pdo->prepare("
INSERT INTO user_messages (sender_id, receiver_id, message)
VALUES (?, ?, ?)
");
$stmt->execute([$user_id, $receiver_id, $message]);
$message_id = $pdo->lastInsertId();
// Get the created message
$stmt = $pdo->prepare("
SELECT
um.*,
u.name as sender_name,
up.profile_image as sender_image
FROM user_messages um
JOIN users u ON um.sender_id = u.id
LEFT JOIN user_profiles up ON u.id = up.user_id
WHERE um.id = ?
");
$stmt->execute([$message_id]);
$new_message = $stmt->fetch(PDO::FETCH_ASSOC);
$new_message['is_sender'] = true;
$new_message['is_read'] = false;
$new_message['message'] = html_entity_decode($new_message['message'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
$new_message['sender_avatar'] = $new_message['sender_image'] ?: getInitialsAvatar($new_message['sender_name']);
echo json_encode(['success' => true, 'message' => $new_message]);
break;
case 'mark_read':
// Mark messages as read
$other_user_id = $_POST['user_id'] ?? null;
if (!$other_user_id || !is_numeric($other_user_id)) {
echo json_encode(['success' => false, 'message' => 'Invalid user ID']);
exit;
}
$other_user_id = (int)$other_user_id;
$stmt = $pdo->prepare("
UPDATE user_messages
SET is_read = TRUE
WHERE sender_id = ? AND receiver_id = ? AND is_read = FALSE
");
$stmt->execute([$other_user_id, $user_id]);
echo json_encode(['success' => true, 'marked' => $stmt->rowCount()]);
break;
case 'get_unread_count':
// Get total unread message count
$stmt = $pdo->prepare("
SELECT COUNT(*) as unread_count
FROM user_messages
WHERE receiver_id = ? AND is_read = FALSE
");
$stmt->execute([$user_id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'unread_count' => (int)$result['unread_count']]);
break;
case 'search_users':
// Search for users to message
$query = trim($_GET['q'] ?? '');
if (empty($query) || strlen($query) < 2) {
echo json_encode(['success' => true, 'users' => []]);
exit;
}
$search_term = '%' . $query . '%';
$stmt = $pdo->prepare("
SELECT
u.id,
u.name,
u.email,
up.profile_image,
up.bio
FROM users u
LEFT JOIN user_profiles up ON u.id = up.user_id
WHERE u.id != ?
AND (u.name LIKE ? OR u.email LIKE ?)
ORDER BY u.name ASC
LIMIT 20
");
$stmt->execute([$user_id, $search_term, $search_term]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($users as &$user) {
$user['avatar'] = formatProfileImage($user['profile_image'], $user['name']);
}
echo json_encode(['success' => true, 'users' => $users]);
break;
case 'mark_unseen':
// Mark all messages in a conversation as unread
if (!$user_id) {
echo json_encode(['success' => false, 'message' => 'Authentication required']);
exit;
}
$other_user_id = $_POST['user_id'] ?? null;
if (!$other_user_id || !is_numeric($other_user_id)) {
echo json_encode(['success' => false, 'message' => 'Invalid user ID']);
exit;
}
$other_user_id = (int)$other_user_id;
if ($other_user_id === $user_id) {
echo json_encode(['success' => false, 'message' => 'Invalid operation']);
exit;
}
// Mark all messages from the other user as unread
$stmt = $pdo->prepare("
UPDATE user_messages
SET is_read = FALSE
WHERE sender_id = ? AND receiver_id = ? AND is_read = TRUE
");
$stmt->execute([$other_user_id, $user_id]);
$marked_count = $stmt->rowCount();
echo json_encode([
'success' => true,
'message' => 'Conversation marked as unread',
'marked_count' => $marked_count
]);
break;
case 'delete_conversation':
// Delete all messages in a conversation between current user and another user
if (!$user_id) {
echo json_encode(['success' => false, 'message' => 'Authentication required']);
exit;
}
$other_user_id = $_POST['user_id'] ?? null;
if (!$other_user_id || !is_numeric($other_user_id)) {
echo json_encode(['success' => false, 'message' => 'Invalid user ID']);
exit;
}
$other_user_id = (int)$other_user_id;
if ($other_user_id === $user_id) {
echo json_encode(['success' => false, 'message' => 'Invalid operation']);
exit;
}
// Delete all messages between the two users
$stmt = $pdo->prepare("
DELETE FROM user_messages
WHERE (sender_id = ? AND receiver_id = ?)
OR (sender_id = ? AND receiver_id = ?)
");
$stmt->execute([$user_id, $other_user_id, $other_user_id, $user_id]);
$deleted_count = $stmt->rowCount();
echo json_encode([
'success' => true,
'message' => 'Conversation deleted successfully',
'deleted_count' => $deleted_count
]);
break;
case 'delete_message':
// Delete a single message (only if user is the sender)
if (!$user_id) {
echo json_encode(['success' => false, 'message' => 'Authentication required']);
exit;
}
$message_id = $_POST['message_id'] ?? null;
if (!$message_id || !is_numeric($message_id)) {
echo json_encode(['success' => false, 'message' => 'Invalid message ID']);
exit;
}
$message_id = (int)$message_id;
// Verify the message exists and belongs to the current user
$stmt = $pdo->prepare("
SELECT id, sender_id
FROM user_messages
WHERE id = ?
");
$stmt->execute([$message_id]);
$message = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$message) {
echo json_encode(['success' => false, 'message' => 'Message not found']);
exit;
}
// Only allow deletion if user is the sender
if ((int)$message['sender_id'] !== $user_id) {
echo json_encode(['success' => false, 'message' => 'You can only delete your own messages']);
exit;
}
// Delete the message
$stmt = $pdo->prepare("DELETE FROM user_messages WHERE id = ?");
$stmt->execute([$message_id]);
echo json_encode([
'success' => true,
'message' => 'Message deleted successfully',
'message_id' => $message_id
]);
break;
case 'forward_message':
// Forward a message to another user
if (!$user_id) {
echo json_encode(['success' => false, 'message' => 'Authentication required']);
exit;
}
$message_id = $_POST['message_id'] ?? null;
$receiver_id = $_POST['receiver_id'] ?? null;
if (!$message_id || !is_numeric($message_id)) {
echo json_encode(['success' => false, 'message' => 'Invalid message ID']);
exit;
}
if (!$receiver_id || !is_numeric($receiver_id)) {
echo json_encode(['success' => false, 'message' => 'Invalid receiver ID']);
exit;
}
$message_id = (int)$message_id;
$receiver_id = (int)$receiver_id;
if ($receiver_id === $user_id) {
echo json_encode(['success' => false, 'message' => 'You cannot forward messages to yourself']);
exit;
}
// Get the original message
$stmt = $pdo->prepare("
SELECT message, sender_id
FROM user_messages
WHERE id = ?
");
$stmt->execute([$message_id]);
$originalMessage = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$originalMessage) {
echo json_encode(['success' => false, 'message' => 'Message not found']);
exit;
}
// Verify receiver exists
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE id = ?");
$stmt->execute([$receiver_id]);
$receiver = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$receiver) {
echo json_encode(['success' => false, 'message' => 'Receiver not found']);
exit;
}
// Create forwarded message with prefix
$forwardedMessage = 'Forwarded: ' . $originalMessage['message'];
$forwardedMessage = mb_substr($forwardedMessage, 0, 5000);
// Insert forwarded message
$stmt = $pdo->prepare("
INSERT INTO user_messages (sender_id, receiver_id, message)
VALUES (?, ?, ?)
");
$stmt->execute([$user_id, $receiver_id, $forwardedMessage]);
$new_message_id = $pdo->lastInsertId();
echo json_encode([
'success' => true,
'message' => 'Message forwarded successfully',
'message_id' => $new_message_id,
'receiver_name' => $receiver['name']
]);
break;
default:
echo json_encode(['success' => false, 'message' => 'Invalid action']);
break;
}
} catch (PDOException $e) {
error_log("Messages API error: " . $e->getMessage());
echo json_encode(['success' => false, 'message' => 'Database error occurred']);
}
// Helper function to format profile image URL
function formatProfileImage($profile_image, $name) {
// If no profile image, return initials
if (empty($profile_image)) {
return getInitialsAvatar($name);
}
// Trim whitespace
$profile_image = trim($profile_image);
// If it's already a full URL (http/https), return as is
if (preg_match('/^https?:\/\//i', $profile_image)) {
return $profile_image;
}
// If it starts with /, it's a valid relative path
if (strpos($profile_image, '/') === 0) {
return $profile_image;
}
// If it looks like a file path but doesn't start with /, try to fix it
// Check if it contains common image extensions
if (preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $profile_image)) {
// If it's in uploads directory, add leading slash
if (strpos($profile_image, 'uploads/') !== false || strpos($profile_image, 'profile') !== false) {
// Check if it already has a directory structure
if (strpos($profile_image, '/') === false) {
// It's just a filename, assume it's in uploads/profile_images/
return '/uploads/profile_images/' . $profile_image;
} else {
// It has a path but no leading slash
return '/' . ltrim($profile_image, '/');
}
}
// If it starts with a directory name (like 'ds/'), try to construct proper path
if (preg_match('/^[a-z0-9_]+\//i', $profile_image)) {
// This might be a malformed path, try to find it in uploads
$filename = basename($profile_image);
return '/uploads/profile_images/' . $filename;
}
}
// If we can't determine a valid image path, return initials
return getInitialsAvatar($name);
}
// Helper function to generate avatar from initials
function getInitialsAvatar($name) {
$initials = '';
$words = explode(' ', trim($name));
foreach ($words as $word) {
if (!empty($word)) {
$initials .= mb_substr($word, 0, 1);
if (mb_strlen($initials) >= 2) break;
}
}
return mb_strtoupper($initials ?: '?');
}