Ryanhub - file viewer
filename: views/api/upload_interactions.php
branch: main
back to repo
<?php
declare(strict_types=1);

// This endpoint is used by the modal via HTML requests (no JSON API).
// It returns ONLY the modal body so the frontend can swap it into the modal.

// Allow GET to be public (counts/comments). Reactions/comments require login.

$uploadId = (int)($_GET['upload_id'] ?? $_POST['upload_id'] ?? 0);
if ($uploadId <= 0) {
    http_response_code(400);
    echo 'Missing upload_id.';
    return;
}

$currentUserId = currentUserId();
$reactionAction = $_POST['reaction'] ?? null; // kept for backward compatibility; modal now returns comments only
$commentBody = $_POST['comment_body'] ?? null;

$isAjax = ($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'XMLHttpRequest';

$errors = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Determine which action this request is for.
    if ($reactionAction !== null) {
        if (!isLoggedIn()) {
            http_response_code(401);
            echo 'Sign in required.';
            return;
        }

        $reaction = $_POST['reaction'] ?? '';
        if ($reaction !== 'like' && $reaction !== 'dislike') {
            $errors[] = 'Invalid reaction.';
        } else {
            $userId = (int)currentUserId();
            $nowStr = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
            // Upsert: unique key (upload_id,user_id) ensures one row per user.
            $stmt = $db->prepare('
                INSERT INTO upload_reactions (upload_id, user_id, reaction, created_at)
                VALUES (?, ?, ?, ?)
                ON DUPLICATE KEY UPDATE reaction = VALUES(reaction), created_at = VALUES(created_at)
            ');
            $stmt->bind_param('iiss', $uploadId, $userId, $reaction, $nowStr);
            $stmt->execute();
        }

        // For async reaction posts from the main page, return JSON counts.
        if ($isAjax) {
            $counts = $db->prepare('
                SELECT
                    (SELECT COUNT(*) FROM upload_reactions r WHERE r.upload_id = ? AND r.reaction = "like") AS like_count,
                    (SELECT COUNT(*) FROM upload_reactions r WHERE r.upload_id = ? AND r.reaction = "dislike") AS dislike_count
            ');
            $counts->bind_param('ii', $uploadId, $uploadId);
            $counts->execute();
            $countsRow = $counts->get_result()->fetch_assoc();

            $myReaction = null;
            if (isLoggedIn()) {
                $stmt2 = $db->prepare('SELECT reaction FROM upload_reactions WHERE upload_id = ? AND user_id = ? LIMIT 1');
                $uid = (int)currentUserId();
                $stmt2->bind_param('ii', $uploadId, $uid);
                $stmt2->execute();
                $row2 = $stmt2->get_result()->fetch_assoc();
                $myReaction = $row2['reaction'] ?? null;
            }

            header('Content-Type: application/json; charset=utf-8');
            echo json_encode([
                'like_count' => (int)($countsRow['like_count'] ?? 0),
                'dislike_count' => (int)($countsRow['dislike_count'] ?? 0),
                'my_reaction' => $myReaction,
            ]);
            return;
        }
    } elseif ($commentBody !== null) {
        if (!isLoggedIn()) {
            http_response_code(401);
            echo 'Sign in required.';
            return;
        }

        $body = trim((string)$commentBody);
        if ($body === '') {
            $errors[] = 'Comment cannot be empty.';
        } else {
            $userId = (int)currentUserId();
            $nowStr = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
            $stmt = $db->prepare('INSERT INTO upload_comments (upload_id, user_id, body, created_at) VALUES (?, ?, ?, ?)');
            $stmt->bind_param('iiss', $uploadId, $userId, $body, $nowStr);
            $stmt->execute();
        }
    } else {
        http_response_code(400);
        echo 'Unsupported action.';
        return;
    }
}

// Comments (newest-first)
$commentsStmt = $db->prepare('
    SELECT c.id, c.user_id, c.body, c.created_at, mp.display_name
    FROM upload_comments c
    JOIN member_profiles mp ON mp.user_id = c.user_id
    WHERE c.upload_id = ?
    ORDER BY c.created_at DESC, c.id DESC
');
$commentsStmt->bind_param('i', $uploadId);
$commentsStmt->execute();
$comments = $commentsStmt->get_result()->fetch_all(MYSQLI_ASSOC);
?>

<div class="upload-interactions-modal-body">
        <div style="margin-bottom: 10px;">
        <?php if ($errors): ?>
            <div style="color: #9b2c2c; font-size: 13px; margin-bottom: 10px;">
                <?php foreach ($errors as $err): ?>
                    <div><?= htmlspecialchars($err, ENT_QUOTES, 'UTF-8') ?></div>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>

        <div class="muted upload-interactions-comments-heading" style="font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; margin-bottom: 8px;">
            Comments
        </div>

        <?php if (!isLoggedIn()): ?>
            <div style="font-size: 13px; color: var(--muted); margin-bottom: 12px;">
                Sign in to leave a comment.
            </div>
        <?php else: ?>
            <form method="post" action="<?= htmlspecialchars(url('api/upload-interactions'), ENT_QUOTES, 'UTF-8') ?>" class="upload-comment-form" style="display:grid; gap: 8px; margin-bottom: 14px;">
                <input type="hidden" name="upload_id" value="<?= (int)$uploadId ?>">
                <label>
                    <span class="muted" style="font-size: 12px;">Add a comment</span><br>
                    <textarea name="comment_body" rows="3" required style="width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12); background: rgba(255,255,255,0.8);"></textarea>
                </label>
                <button type="submit" class="pill pill-accent" style="justify-content:center; max-width: 160px;">
                    Post Comment
                </button>
            </form>
        <?php endif; ?>
    </div>

    <?php if (!$comments): ?>
        <div style="font-size: 13px; color: var(--muted);">
            No comments yet.
        </div>
    <?php else: ?>
        <div style="display:grid; gap: 10px;">
            <?php foreach ($comments as $c): ?>
                <div style="border-radius: 14px; border: 1px solid rgba(0,0,0,0.06); background: rgba(255,255,255,0.75); padding: 10px 12px;">
                    <div style="display:flex; justify-content: space-between; gap: 12px; margin-bottom: 6px;">
                        <div style="font-size: 12px; color: var(--muted);">
                            <?= htmlspecialchars($c['display_name'], ENT_QUOTES, 'UTF-8') ?>
                        </div>
                        <div style="font-size: 11px; color: rgba(0,0,0,0.45);">
                            <?= htmlspecialchars(date('M j, Y', strtotime($c['created_at'])), ENT_QUOTES, 'UTF-8') ?>
                        </div>
                    </div>
                    <div style="font-size: 13px; line-height: 1.5;">
                        <?= nl2br(htmlspecialchars($c['body'], ENT_QUOTES, 'UTF-8')) ?>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>
    <?php endif; ?>
</div>