"Fail"]; if (isLoggedIn() && fileExists($imageID, false) && !empty($title) && !empty($memeText) && !empty($imageID) && $imageID > 0) { $stmtMemeAdd = $mysqli->prepare('INSERT INTO Memes (AuthorID, Title, TextContent, FileID) VALUES (?, ?, ?, ?)'); $stmtMemeAdd->bind_param('issi', $_SESSION['ID'], htmlspecialchars($title), htmlspecialchars($memeText), $imageID); if ($stmtMemeAdd->execute() && $stmtMemeAdd->affected_rows > 0) { $output["Status"] = "Success"; $output["Meme"] = "Funny"; } } return $output; } function executeAndRenderMemes(mysqli_stmt $stmt): string { global $routerConfig; $stmt->execute(); $stmt->bind_result($memeID, $title, $textContent, $createdAt, $authorID, $filePath, $imageWidth, $imageHeight, $userNickname); $memes_out = ''; $meme_template = file_get_contents($routerConfig['template_dir'] . "meme.html"); $meme_gallery_template = file_get_contents($routerConfig['template_dir'] . 'meme_gallery.html'); while ($stmt->fetch()) { $memes_out .= renderMeme($memeID, $authorID, $title, $textContent, $createdAt, $filePath, $imageWidth, $imageHeight, $userNickname, $meme_template); } $meme_add = isLoggedIn() ? file_get_contents($routerConfig['template_dir'] . 'meme_add.html') : ''; $meme_gallery_out = str_replace('__TEMPLATE_MEMES_HERE__', $memes_out, $meme_gallery_template); $meme_gallery_out = str_replace('__TEMPLATE_MEME_ADD__', $meme_add, $meme_gallery_out); $stmt->close(); return $meme_gallery_out; } /** * Renders a meme into HTML based on provided data and a template. * * @param int $id The ID of the meme. * @param int $authorId The author's user ID. * @param string $title The title of the meme. * @param string $textContent The text content of the meme. * @param string $createdAt The creation timestamp of the meme. * @param string $filePath The file path of the associated image. * @param int $imageWidth The width of the image. * @param int $imageHeight The height of the image. * @param string $userNickname The nickname of the meme's author. * @param string $meme_template The HTML template for a meme. (used to not read the template over and over when rendering more memes) * @return string Returns the rendered HTML of the meme. */ function renderMeme(int $id, int $authorId, string $title, string $textContent, string $createdAt, string $filePath, int $imageWidth, int $imageHeight, string $userNickname, string $meme_template): string { $meme_out = str_replace('__TEMPLATE_MEME_TITLE__', htmlspecialchars($title), $meme_template); $meme_out = str_replace('__TEMPLATE_MEME_AUTHOR__', htmlspecialchars($userNickname), $meme_out); $meme_out = str_replace('__TEMPLATE_MEME_DATE__', htmlspecialchars($createdAt), $meme_out); $meme_out = str_replace('__TEMPLATE_MEME_IMAGE__', '/' . htmlspecialchars($filePath), $meme_out); $meme_out = str_replace('__TEMPLATE_MEME_IMAGE_WIDTH__', strval($imageWidth), $meme_out); $meme_out = str_replace('__TEMPLATE_MEME_IMAGE_HEIGHT__', strval($imageHeight), $meme_out); $meme_out = str_replace('__TEMPLATE_MEME_DELETE_BUTTON__', (isModerator() || $_SESSION['ID'] == $authorId) ? "" : '', $meme_out); $meme_votes = calculateNetVotes($id); $meme_net_votes = $meme_votes['NetVotes']; if ($meme_votes['UserVote'] > 0) { $meme_upvote_active = 'fill'; $meme_downvote_active = 'line'; $meme_vote_counter_class = 'positive'; $meme_upvote_button_class = ' visual_hover'; $meme_downvote_button_class = ''; } elseif (($meme_votes['UserVote'] < 0)) { $meme_upvote_active = 'line'; $meme_downvote_active = 'fill'; $meme_vote_counter_class = 'negative'; $meme_upvote_button_class = ''; $meme_downvote_button_class = ' visual_hover'; } else { $meme_downvote_active = 'line'; $meme_upvote_active = 'line'; $meme_vote_counter_class = 'neutral'; $meme_upvote_button_class = ''; $meme_downvote_button_class = ''; } $meme_upvote = isLoggedIn() ? "" : ''; $meme_downvote = isLoggedIn() ? "" : ''; $meme_out = str_replace('__TEMPLATE_MEME_VOTES_NUMBER__', strval($meme_net_votes), $meme_out); $meme_out = str_replace('__TEMPLATE_MEME_UPVOTE__', $meme_upvote, $meme_out); $meme_out = str_replace('__TEMPLATE_MEME_DOWNVOTE__', $meme_downvote, $meme_out); $meme_out = str_replace('__TEMPLATE_MEME_ID__', strval($id), $meme_out); $meme_out = str_replace('__TEMPLATE_MEME_VOTE_COUNTER_CLASS__', $meme_vote_counter_class, $meme_out); return str_replace('__TEMPLATE_MEME_TEXT__', htmlspecialchars($textContent), $meme_out); } /** * Renders a gallery of memes, optionally filtered by author ID, meme ID, or a keyword. * * This function retrieves memes from the database and returns an HTML string representation. * It supports filtering by author ID, meme ID, or a keyword that is searched in titles and text content. * It also supports pagination through an offset parameter. * * @param int|null $offset Pagination offset, used to calculate the starting point for records to return. * @param int|null $authorId Optional author ID for filtering memes by a specific author. * @param int|null $memeId Optional meme ID for rendering a single meme. * @param string|null $keyword Optional keyword for full-text search in meme titles and content. * @return string Returns the complete HTML content of the meme gallery, optionally filtered. */ function renderMemeGallery(?int $offset = null, ?int $authorId = null, ?int $memeId = null, ?string $keyword = null): string { global $mysqli, $routerConfig; // Start building the SQL query $query = 'SELECT Memes.ID, Memes.Title, Memes.TextContent, Memes.CreatedAt, Memes.AuthorID, Files.Path, Files.Width, Files.Height, Users.Nickname FROM Memes INNER JOIN Users ON Memes.AuthorID = Users.ID INNER JOIN Files ON Memes.FileID = Files.ID'; $conditions = []; $params = []; $types = ''; // Add conditions based on provided parameters if ($authorId !== null) { $conditions[] = 'Memes.AuthorID = ?'; $params[] = $authorId; $types .= 'i'; } if ($memeId !== null) { $conditions[] = 'Memes.ID = ?'; $params[] = $memeId; $types .= 'i'; } if ($keyword !== null) { $conditions[] = '(Memes.Title LIKE CONCAT("%", ?, "%") OR Memes.TextContent LIKE CONCAT("%", ?, "%"))'; $params[] = $keyword; $params[] = $keyword; $types .= 'ss'; } // Append conditions to the query if (!empty($conditions)) { $query .= ' WHERE ' . join(' AND ', $conditions); } if($offset == null) { $offset = 0; } // Add pagination and limit $query .= ' LIMIT ? OFFSET ?'; $params[] = $routerConfig['meme']['per_page']; $params[] = $routerConfig['meme']['per_page'] * $offset; $types .= 'ii'; $stmt = $mysqli->prepare($query); $stmt->bind_param($types, ...$params); return executeAndRenderMemes($stmt); } /** * Deletes a meme from the database if the current user has the right permissions. * * @param int $memeID The ID of the meme to delete. * @return array Returns an associative array with the status of the operation. * @global mysqli $mysqli The database connection object. */ function deleteMeme(int $memeID): array { global $mysqli; $out = ["Status" => "Fail"]; if (isLoggedIn()) { $query = !isModerator() ? 'DELETE FROM Memes WHERE ID = ? AND AuthorID = ?' : 'DELETE FROM Memes WHERE ID = ?'; $stmtDelete = $mysqli->prepare($query); if (!isModerator()) { $stmtDelete->bind_param('ii', $memeID, $_SESSION['ID']); } else { $stmtDelete->bind_param('i', $memeID); } $stmtDelete->execute(); if ($stmtDelete->affected_rows > 0) { $out['Status'] = 'Success'; } $stmtDelete->close(); } return $out; } /** * Records or updates a vote on a meme by the current user. * * @param int $memeID The ID of the meme to be voted on. * @param int $isUpvote Indicates whether the vote is an upvote (1) or downvote (0). * @return array Returns an associative array with the status of the vote operation. * @global mysqli $mysqli The database connection object. */ function voteMeme(int $memeID, int $isUpvote): array { global $mysqli; $out = ["Status" => "Fail"]; if ($isUpvote != 1) { $isUpvote = 0; } $memeVoteConn = $mysqli->prepare('INSERT INTO MemeVotes (MemeID, UserID, isUpvote) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE isUpvote = VALUES(isUpvote)'); $memeVoteConn->bind_param('iii', $memeID, $_SESSION['ID'], $isUpvote); $memeVoteConn->execute(); if ($memeVoteConn->affected_rows > 0) { $out['Status'] = 'Success'; } $memeVoteConn->close(); return $out; } /** * Deletes a vote previously made by the current user on a meme. * * @param int $memeID The ID of the meme whose vote is to be deleted. * @return array Returns an associative array with the status of the deletion. * @global mysqli $mysqli The database connection object. */ function deleteVoteMeme(int $memeID): array { global $mysqli; $out = ["Status" => "Fail"]; $memeVoteConn = $mysqli->prepare('DELETE FROM MemeVotes WHERE MemeID = ? AND UserID = ?'); $memeVoteConn->bind_param('ii', $memeID, $_SESSION['ID']); $memeVoteConn->execute(); if ($memeVoteConn->affected_rows > 0) { $out['Status'] = 'Success'; } $memeVoteConn->close(); return $out; } /** * Calculates the net votes for a meme and determines if the current user has voted on it. * The array has both the net votes and the user vote(0 when the user hasn't voted) * * @param int $memeID The ID of the meme for which votes are being calculated. * @return array Returns an array with net votes and the user's vote status. * @global mysqli $mysqli The database connection object. */ function calculateNetVotes(int $memeID): array { global $mysqli; // Adjusted query to calculate net votes and get the user's vote in one go $query = " SELECT SUM(CASE WHEN isUpvote = 1 THEN 1 ELSE -1 END) AS NetVotes, ( SELECT CASE WHEN isUpvote = 1 THEN 1 ELSE -1 END FROM MemeVotes WHERE MemeID = ? AND UserID = ? ) AS UserVote FROM MemeVotes WHERE MemeID = ?"; $stmt = $mysqli->prepare($query); $userID = $_SESSION['ID']; $stmt->bind_param('iii', $memeID, $userID, $memeID); $stmt->execute(); $result = $stmt->get_result(); $data = $result->fetch_assoc(); $netVotes = $data['NetVotes'] ?? 0; // Null coalescing operator in case no votes are found $userVote = $data['UserVote'] ?? 0; // Default to 0 if the user hasn't voted $stmt->close(); return [ "NetVotes" => $netVotes, "UserVote" => $userVote ]; } /** * Fetches the net votes and user's vote status for a specific meme. * Essentially just a wrapper of getMemeVotes for an API call * * @param int $memeID The ID of the meme to fetch votes for. * @return array Returns an array with the net votes and the user's vote status, along with operation status. */ function getMemeVotes(int $memeID): array { $voteData = calculateNetVotes($memeID); return [ "Status" => "Success", "NetVotes" => $voteData['NetVotes'], "UserVote" => $voteData['UserVote'] ]; }