diff options
| author | ilotterytea <iltsu@alright.party> | 2025-12-08 21:53:36 +0500 |
|---|---|---|
| committer | ilotterytea <iltsu@alright.party> | 2025-12-08 21:53:36 +0500 |
| commit | 57472eab3c7b035392c6a5aa240593ecaa7d1ccf (patch) | |
| tree | 9da30829290f225be2dab3d383549cbfda82ed19 /emotes | |
| parent | 6541d0f3888862ab049055fd418b700f73eed367 (diff) | |
upd: moved all /public/ files to the root folder
Diffstat (limited to 'emotes')
| -rw-r--r-- | emotes/delete.php | 47 | ||||
| -rw-r--r-- | emotes/index.php | 546 | ||||
| -rw-r--r-- | emotes/rate.php | 63 | ||||
| -rw-r--r-- | emotes/setmanip.php | 138 | ||||
| -rw-r--r-- | emotes/upload.php | 552 |
5 files changed, 1346 insertions, 0 deletions
diff --git a/emotes/delete.php b/emotes/delete.php new file mode 100644 index 0000000..6252e45 --- /dev/null +++ b/emotes/delete.php @@ -0,0 +1,47 @@ +<?php +include_once "../../src/alert.php"; +include_once "../../src/config.php"; +include_once "../../src/accounts.php"; + +if (!authorize_user(true)) { + generate_alert("/account", "Not authorized", 403); + exit; +} + +if (!isset($_POST["id"])) { + generate_alert("/emotes", "Emote ID is not specified"); + exit; +} + +$emote_id = $_POST["id"]; +$user_id = $_SESSION["user_id"]; + +$db = new PDO(DB_URL, DB_USER, DB_PASS); + +$stmt = $db->prepare("SELECT uploaded_by, code FROM emotes WHERE id = ?"); +$stmt->execute([$emote_id]); + +if ($row = $stmt->fetch()) { + if ($row["uploaded_by"] === $user_id) { + $unlink = intval($_POST["unlink"] ?? "0") == 1; + + if ($unlink) { + $stmt = $db->prepare("UPDATE emotes SET uploaded_by = NULL WHERE id = ? AND uploaded_by = ?"); + $stmt->execute([$emote_id, $user_id]); + generate_alert("/emotes/?id=$emote_id", 'Your authorship has been removed for the emote "' . $row["code"] . '"', 200); + } else { + $stmt = $db->prepare("DELETE FROM emotes WHERE id = ? AND uploaded_by = ?"); + $stmt->execute([$emote_id, $user_id]); + + $path = $_SERVER["DOCUMENT_ROOT"] . "/static/userdata/emotes/$emote_id"; + array_map("unlink", glob("$path/*.*")); + rmdir($path); + + generate_alert("/emotes", 'Emote "' . $row["code"] . '" has been removed from the servers', 200); + } + } else { + generate_alert("/emotes", "You don't own the emote \"" . $row["code"] . "\"", 403); + } +} else { + generate_alert("/emotes", "Emote ID $emote_id not found", 404); +}
\ No newline at end of file diff --git a/emotes/index.php b/emotes/index.php new file mode 100644 index 0000000..af14120 --- /dev/null +++ b/emotes/index.php @@ -0,0 +1,546 @@ +<?php +include "../../src/emote.php"; +include "../../src/accounts.php"; +include_once "../../src/config.php"; +include "../../src/partials.php"; +include "../../src/utils.php"; +include "../../src/alert.php"; + +authorize_user(); + +$db = new PDO(DB_URL, DB_USER, DB_PASS); + +$user_id = $_SESSION["user_id"] ?? ""; + +$emotes = null; +$emote = null; +$total_emotes = 0; +$total_pages = 0; + +// fetching emote by id +if (isset($_GET["id"])) { + $id = $_GET["id"]; + + $stmt = $db->prepare("SELECT e.id, e.code, e.created_at, e.source, e.visibility, + COALESCE(COUNT(r.rate), 0) as total_rating, + COALESCE(ROUND(AVG(r.rate), 2), 0) AS average_rating, + CASE WHEN up.private_profile = FALSE OR up.id = ? THEN e.uploaded_by ELSE NULL END AS uploaded_by + FROM emotes e + LEFT JOIN user_preferences up ON up.id = e.uploaded_by + LEFT JOIN ratings AS r ON r.emote_id = e.id + WHERE e.id = ? + LIMIT 1 + "); + $stmt->execute([$user_id, $id]); + + $row = $stmt->fetch(); + + if ($row["id"]) { + // fetching emote tags + $stmt = $db->prepare("SELECT t.code FROM tags t + INNER JOIN tag_assigns ta ON ta.emote_id = ? + WHERE t.id = ta.tag_id + "); + $stmt->execute([$row["id"]]); + $tags = $stmt->fetchAll(PDO::FETCH_ASSOC); + $tags = array_column($tags, "code"); + + $row["tags"] = $tags; + $row["ext"] = "webp"; + $emote = Emote::from_array_with_user($row, $db); + } else { + generate_alert("/404.php", "Emote ID $id does not exists", 404); + exit; + } +} +// fetching all emotes +else { + $sort = $_GET["sort"] ?? "high_ratings"; + $sort = match ($sort) { + "low_ratings" => "rating ASC", + "recent" => "e.created_at DESC", + "oldest" => "e.created_at ASC", + default => "rating DESC" + }; + $page = max(1, intval($_GET["p"] ?? "1")); + $limit = 50; + $offset = ($page - 1) * $limit; + $search = $_GET["q"] ?? ""; + + // fetching emotes + $stmt = $db->prepare("SELECT e.*, + CASE WHEN up.private_profile = FALSE OR up.id = ? THEN e.uploaded_by ELSE NULL END AS uploaded_by, + CASE WHEN EXISTS ( + SELECT 1 + FROM emote_set_contents ec + INNER JOIN emote_sets es ON es.id = ec.emote_set_id + JOIN acquired_emote_sets aes ON aes.emote_set_id = es.id + WHERE ec.emote_id = e.id AND es.id = ? + ) THEN 1 ELSE 0 END AS is_in_user_set, COALESCE(COUNT(r.rate), 0) AS rating + FROM emotes e + LEFT JOIN user_preferences up ON up.id = e.uploaded_by + LEFT JOIN ratings AS r ON r.emote_id = e.id + LEFT JOIN tag_assigns ta ON ta.emote_id = e.id + LEFT JOIN tags t ON t.id = ta.tag_id + WHERE (t.code = ? OR e.code LIKE ?) AND e.visibility = 1 + GROUP BY + e.id, e.code, e.created_at + ORDER BY $sort + LIMIT ? OFFSET ? + "); + + $sql_search = "%$search%"; + $user_emote_set_id = $_SESSION["user_active_emote_set_id"] ?? ""; + + $stmt->bindParam(1, $user_id, PDO::PARAM_STR); + $stmt->bindParam(2, $user_emote_set_id, PDO::PARAM_STR); + $stmt->bindParam(3, $search, PDO::PARAM_STR); + $stmt->bindParam(4, $sql_search, PDO::PARAM_STR); + $stmt->bindParam(5, $limit, PDO::PARAM_INT); + $stmt->bindParam(6, $offset, PDO::PARAM_INT); + + $stmt->execute(); + + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + $emotes = []; + + foreach ($rows as $row) { + array_push($emotes, Emote::from_array_with_user($row, $db)); + } + + $total_emotes = count($emotes); + $total_pages = ceil($total_emotes / $limit); +} + +if (CLIENT_REQUIRES_JSON) { + json_response([ + "status_code" => 200, + "message" => null, + "data" => $emotes ?? $emote + ]); + exit; +} +?> + +<html> + +<head> + <title><?php + echo ($emote != null ? "Emote " . $emote->get_code() : "Emotes") . ' - ' . INSTANCE_NAME + ?></title> + <link rel="stylesheet" href="/static/style.css"> + <link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon"> +</head> + +<body> + <div class="container"> + <div class="wrapper"> + <?php html_navigation_bar() ?> + + <section class="content row"> + <section class="sidebar"> + <?php + html_navigation_search(); + html_featured_emote($db); + html_random_emote($db); + ?> + </section> + <section class="content"> + <?php display_alert() ?> + <section class="box"> + <div class="box navtab row"> + <?php + if ($emote != null) { + echo "Emote - " . $emote->get_code(); + echo '<div class="row small-gap" style="margin-left:auto">'; + + $original_path = "/static/userdata/emotes/" . $emote->get_id(); + $files = glob($_SERVER["DOCUMENT_ROOT"] . $original_path . "/original.*"); + + if (!empty($files)) { + $filename = basename($files[0]); + echo "<a href='$original_path/$filename' target='_BLANK'><img src='/static/img/icons/emotes/emote.png' alt='[Show original]' title='Show original' /></a>"; + } + + $stmt = $db->prepare(" + SELECT MAX(es.is_featured) AS is_featured, MAX(es.is_global) AS is_global + FROM emote_sets es + JOIN emote_set_contents esc ON esc.emote_set_id = es.id + JOIN emotes e ON esc.emote_id = e.id + WHERE e.id = ? + "); + $stmt->execute([$emote->get_id()]); + + if ($row = $stmt->fetch()) { + if ($row["is_featured"]) { + echo '<img src="/static/img/icons/star.png" title="Featured emote" alt="Featured" />'; + } + if ($row["is_global"]) { + echo '<img src="/static/img/icons/world.png" title="Global emote" alt="Global" />'; + } + } + echo '</div>'; + } else { + echo "<div class='grow'>Emotes - Page $page/$total_pages</div>"; + html_emotelist_mode(); + } + ?> + </div> + <?php + if ($emote != null) { ?> + <div class="box content"> + <div class="emote-showcase items-bottom"> + <?php + for ($size = 1; $size < 4; $size++) { + echo '<div class="column items-center small-gap">'; + + echo '<img src="/static/userdata/emotes/'; + echo $emote->get_id(); + echo "/{$size}x.webp\""; + echo 'title="' . $emote->get_code() . '" />'; + + $path = $_SERVER["DOCUMENT_ROOT"] . '/static/userdata/emotes/' . $emote->get_id() . "/{$size}x.webp"; + + echo '<div class="column items-center">'; + + if ($file_size = filesize($path)) { + $kb = sprintf("%.2f", $file_size / 1024); + echo "<p class='font-small'>{$kb}KB</p>"; + } + + if ($image_size = getimagesize($path)) { + echo "<p class='font-small'>$image_size[0]x$image_size[1]</p>"; + } + + echo '</div></div>'; + } + ?> + </div> + </div> + </section> + <section class="box items row"> + <?php if (isset($_SESSION["user_id"])) { + echo '' ?> + <div class="items row left full"> + <?php + $added = false; + + if (isset($_SESSION["user_active_emote_set_id"])) { + $stmt = $db->prepare("SELECT id, code FROM emote_set_contents WHERE emote_set_id = ? AND emote_id = ?"); + $stmt->execute([$_SESSION["user_active_emote_set_id"], $emote->get_id()]); + + $added = false; + + if ($row = $stmt->fetch()) { + $added = true; + $emote_current_name = $row["code"] ?? $emote->get_code(); + } + } + + if (isset($_SESSION["user_role"]) && $_SESSION["user_role"]["permission_emoteset_own"]) { + echo '' ?> + <form action="/emotes/setmanip.php" method="POST"> + <input type="text" name="id" value="<?php echo $emote->get_id() ?>" + style="display: none;"> + <input type="text" name="emote_set_id" + value="<?php echo $_SESSION["user_active_emote_set_id"] ?>" style="display: none;"> + <?php + if ($added) { + ?> + <input type="text" name="action" value="remove" style="display: none;"> + <button type="submit" class="red">Remove from my channel</button> + </form> + <form action="/emotes/setmanip.php" method="POST" class="row"> + <input type="text" name="id" value="<?php echo $emote->get_id() ?>" + style="display: none;"> + <input type="text" name="emote_set_id" + value="<?php echo $_SESSION["user_active_emote_set_id"] ?>" style="display: none;"> + <input type="text" name="value" id="emote-alias-input" + value="<?php echo $emote_current_name ?>" + placeholder="<?php echo $emote->get_code() ?>"> + <input type="text" name="action" value="alias" style="display: none;"> + <button type="submit" class="transparent"><img src="/static/img/icons/pencil.png" + alt="Rename" title="Rename"></button> + <?php + } else { ?> + <input type="text" name="action" value="add" style="display: none;"> + <button type="submit" class="green">Add to my channel</button> + <?php + } + ?> + </form> + <?php + ; + } + ?> + + <?php if ($emote->get_uploaded_by() === $_SESSION["user_id"]): ?> + <form action="/emotes/delete.php" method="post"> + <input type="text" name="id" value="<?php echo $emote->get_id() ?>" + style="display: none;"> + <button type="submit" class="transparent"> + <img src="/static/img/icons/bin.png" alt="Delete emote" title="Delete emote"> + </button> + </form> + <form action="/emotes/delete.php" method="post"> + <input type="text" name="id" value="<?php echo $emote->get_id() ?>" + style="display: none;"> + <input type="text" name="unlink" value="1" style="display:none"> + <button type="submit" class="transparent"> + <img src="/static/img/icons/link_break.png" alt="Remove your authorship" + title="Remove your authorship"> + </button> + </form> + <?php endif; ?> + </div> + <div class="items row right full"> + <?php + if (isset($_SESSION["user_role"])) { + if ($_SESSION["user_role"]["permission_rate"]) { + $stmt = $db->prepare("SELECT rate FROM ratings WHERE user_id = ? AND emote_id = ?"); + $stmt->execute([$_SESSION["user_id"], $id]); + + if ($row = $stmt->fetch()) { + echo 'You gave <img src="/static/img/icons/ratings/' . $row["rate"] . '.png" width="16" height="16"'; + echo 'title="' . RATING_NAMES[$row["rate"]] . '">'; + } else { + foreach (RATING_NAMES as $key => $value) { + echo '<form action="/emotes/rate.php" method="POST">'; + echo '<input type="text" name="id" value="' . $emote->get_id() . '"style="display: none;">'; + echo "<input type=\"text\" name=\"rate\" value=\"$key\" style=\"display:none;\">"; + echo '<button type="submit" class="transparent">'; + echo "<img + src=\"/static/img/icons/ratings/$key.png\" alt=\"$value!\" + title=\"IT'S A $value!\">"; + echo '</button></form>'; + } + } + } + if (REPORTS_ENABLE && $_SESSION["user_role"]["permission_report"]) { + echo "<a class='button red' href='/report?emote_id={$emote->id}'>Report emote</a>"; + } + } + ?> + </div> + <?php + } else { + echo '' ?> + <p><a href="/account/login">Log in</a> to get additional features...</p> + <?php + } + ?> + </section> + + <section class="box"> + <table class="vertical"> + <?php if (!empty($emote->get_tags())): ?> + <tr> + <th>Tags</th> + <td> + <?php + foreach ($emote->get_tags() as $tag) { + echo "<a href='/emotes/?q=$tag'>$tag</a> "; + } + ?> + </td> + </tr> + <?php endif; ?> + <tr> + <th>Uploader</th> + <td><?php + $username = ANONYMOUS_DEFAULT_NAME; + $link = "#"; + $show_private_badge = false; + $badge = null; + $custom_badge = null; + + if ($emote->get_uploaded_by()) { + $u = $emote->get_uploaded_by(); + $show_private_badge = $u->private_profile; + + $username = $u->username; + $link = "/users.php?id={$u->id}"; + $badge = $u->role; + $custom_badge = $u->custom_badge; + } + + echo "<a href=\"$link\">"; + echo $username; + echo "</a>"; + + if ($show_private_badge) { + echo " <img src='/static/img/icons/eye.png' alt='(Private)' title='You are the only one who sees this' />"; + } + + if ($badge && $badge->badge) { + echo " <img src='/static/userdata/badges/{$badge->badge->id}/1x.webp' alt='## {$badge->name}' title='{$badge->name}' />"; + } + + if ($custom_badge) { + echo " <img src='/static/userdata/badges/{$custom_badge->id}/1x.webp' alt='' title='Personal badge' />"; + } + + echo ', <span title="'; + echo date("M d, Y H:i:s", $emote->get_created_at()); + echo ' UTC">about ' . format_timestamp(time() - $emote->get_created_at()) . " ago</span>"; + ?></td> + </tr> + <?php + $stmt = $db->prepare("SELECT u.id, a.created_at FROM users u + INNER JOIN mod_actions a ON a.emote_id = ? + WHERE u.id = a.user_id"); + $stmt->execute([$emote->get_id()]); + + if ($row = $stmt->fetch()) { + $approver = User::get_user_by_id($db, $row["id"]); + + echo '<tr><th>Approver</th><td>'; + echo "<a href='/users.php?id={$approver->id}' target='_blank'>{$approver->username}</a>"; + + if ($approver->role && $approver->role->badge) { + echo " <img src='/static/userdata/badges/{$approver->role->badge->id}/1x.webp' alt='## {$approver->role->name}' title='{$approver->role->name}' />"; + } + + if ($approver->custom_badge) { + echo " <img src='/static/userdata/badges/{$approver->custom_badge->id}/1x.webp' alt='' title='Personal badge' />"; + } + + echo ', <span title="'; + echo date("M d, Y H:i:s", strtotime($row["created_at"])) . ' UTC">'; + echo format_timestamp(strtotime($row["created_at"]) - $emote->get_created_at()) . ' after upload'; + echo '</span></td></tr>'; + } + + if (RATING_ENABLE): ?> + <tr> + <th>Rating</th> + <?php + if ($emote->get_rating()["total"] < RATING_EMOTE_MIN_VOTES) { + echo '<td>Not rated (' . $emote->get_rating()["total"] . ')</td>'; + } else { + + $rating = $emote->get_rating()["average"]; + + // TODO: make it customizable + list($rating_classname, $rating_name) = match (true) { + in_range($rating, 0.75, 1.0) => [ + "gemerald", + "<img src='/static/img/icons/ratings/1.png'> + <img src='/static/img/icons/ratings/1.png'> + <img src='/static/img/icons/ratings/1.png'> Shiny Gemerald! + <img src='/static/img/icons/ratings/1.png'> + <img src='/static/img/icons/ratings/1.png'> + <img src='/static/img/icons/ratings/1.png'> + " + ], + in_range($rating, 0.25, 0.75) => ["gem", "<img src='/static/img/icons/ratings/1.png'> Gem <img src='/static/img/icons/ratings/1.png'>"], + in_range($rating, -0.25, 0.25) => ["iron", "Iron"], + in_range($rating, -0.75, -0.25) => ["coal", "<img src='/static/img/icons/ratings/-1.png'> Coal <img src='/static/img/icons/ratings/-1.png'>"], + in_range($rating, -1.0, -0.75) => [ + "brimstone", + " + <img src='/static/img/icons/ratings/brimstone.webp'> + <img src='/static/img/icons/ratings/-1.png'> + <img src='/static/img/icons/ratings/brimstone.webp'> + !!!AVOID THIS CANCER-GIVING BRIMSTONE!!! + <img src='/static/img/icons/ratings/brimstone.webp'> + <img src='/static/img/icons/ratings/-1.png'> + <img src='/static/img/icons/ratings/brimstone.webp'> + " + ] + }; + + echo '<td>'; + echo "<span class=\"rating $rating_classname\">$rating_name</span>"; + echo ' (' . $emote->get_rating()["total"] . ')'; + echo '</td>'; + } + ?> + </tr> + <?php endif; ?> + <tr> + <th>Visibility</th> + <td><?php + switch ($emote->get_visibility()) { + case 0: + echo 'Unlisted'; + break; + case 1: + echo 'Public'; + break; + case 2: + echo 'Pending approval (unlisted for a moment)'; + break; + default: + echo 'N/A'; + break; + } + ?></td> + </tr> + <?php if ($emote->get_source()): ?> + <tr> + <th>Source</th> + <td> + <a href="<?php echo $emote->get_source() ?>" + target="_blank"><?php echo $emote->get_source() ?></a> + </td> + </tr> + <?php endif; ?> + </table> + </section> + + <section class="box"> + <div class="content"> + <?php + $stmt = $db->prepare("SELECT users.id, users.username + FROM users + INNER JOIN emote_sets AS es ON es.owner_id = users.id + INNER JOIN emote_set_contents AS ec ON ec.emote_set_id = es.id + INNER JOIN acquired_emote_sets AS aes ON aes.emote_set_id = es.id + WHERE ec.emote_id = ? AND aes.is_default = TRUE"); + + $stmt->execute([$emote->get_id()]); + $count = $stmt->rowCount(); + + $db = null; + + if ($count > 0) { + echo "<p>Added in $count channels</p>"; + } else { + echo "No one has added this emote yet... :'("; + } + ?> + <div class="items row"> + <?php + while ($row = $stmt->fetch()) { + echo '<a href="/users.php?id=' . $row["id"] . '">' . $row["username"] . '</a>'; + } + ?> + </div> + </div> + <?php + } else { ?> + <div class="box content items"> + <?php html_display_emotes($emotes); ?> + </div> + <?php if ($total_pages > 1) { + echo '' ?> + </section> + <section class="box center row"> + <?php + html_pagination( + $total_pages, + $page, + "/emotes?q=" . substr($search, 1, strlen($search) - 2) . "&sort_by=$sort_by" + ); + } + } + ?> + </section> + </section> + </section> + </div> + </div> +</body> + +</html>
\ No newline at end of file diff --git a/emotes/rate.php b/emotes/rate.php new file mode 100644 index 0000000..1e8eb67 --- /dev/null +++ b/emotes/rate.php @@ -0,0 +1,63 @@ +<?php +include_once "../../src/alert.php"; +include_once "../../src/utils.php"; +include_once "../../src/config.php"; +include_once "../../src/accounts.php"; + +if (!RATING_ENABLE) { + generate_alert("/404.php", "Emote ratings are disabled", 403); + exit; +} + +if (!authorize_user(true)) { + exit; +} + +if (isset($_SESSION["user_role"]) && !$_SESSION["user_role"]["permission_rate"]) { + generate_alert("/404.php", "Not enough permissions", 403); + exit; +} + +$id = str_safe($_POST["id"] ?? "0", 32); +$rate = intval(str_safe($_POST["rate"] ?? "0", 2)); + +if ($id == 0 || $rate == 0) { + generate_alert("/emotes" . (isset($_POST["id"]) ? "?id=" . $_POST["id"] : ""), "Not enough POST fields"); + exit; +} + +$db = new PDO(DB_URL, DB_USER, DB_PASS); + +// checking if emote exists +$stmt = $db->prepare("SELECT id FROM emotes WHERE id = ?"); +$stmt->execute([$id]); +if ($stmt->rowCount() != 1) { + generate_alert("/emotes", "Emote ID $id does not exist", 404); + exit; +} + +// checking if user has already given a rate +$stmt = $db->prepare("SELECT id FROM ratings WHERE user_id = ? AND emote_id = ?"); +$stmt->execute([$_SESSION["user_id"], $id]); +if ($stmt->rowCount() != 0) { + generate_alert("/emotes?id=$id", "You have already given a rate for this emote!", 403); + exit; +} + +// giving a rate +$stmt = $db->prepare("INSERT INTO ratings(user_id, emote_id, rate) VALUES (?, ?, ?)"); +$stmt->execute([$_SESSION["user_id"], $id, clamp($rate, -2, 2)]); + +if (CLIENT_REQUIRES_JSON) { + $stmt = $db->prepare("SELECT * FROM ratings WHERE id = ?"); + $stmt->execute([$db->lastInsertId()]); + + json_response([ + "status_code" => 200, + "message" => "Rated!", + "data" => $stmt->fetch(PDO::FETCH_ASSOC) + ]); + exit; +} + +generate_alert("/emotes?id=$id", "Rated!", 200); diff --git a/emotes/setmanip.php b/emotes/setmanip.php new file mode 100644 index 0000000..129790d --- /dev/null +++ b/emotes/setmanip.php @@ -0,0 +1,138 @@ +<?php +include_once "../../src/config.php"; +include "../../src/accounts.php"; +include "../../src/alert.php"; +include_once "../../src/utils.php"; + +if (!authorize_user(true)) { + return; +} + +if (isset($_SESSION["user_role"]) && !$_SESSION["user_role"]["permission_emoteset_own"]) { + generate_alert("/404.php", "Not enough permissions", 403); + exit; +} + +if (!isset($_POST["id"], $_POST["action"], $_POST["emote_set_id"])) { + generate_alert("/emotes", "Not enough POST fields"); + exit; +} + +$db = new PDO(DB_URL, DB_USER, DB_PASS); + +// checking emote +$emote_id = $_POST["id"]; +$stmt = $db->prepare("SELECT id, code, uploaded_by, visibility, created_at FROM emotes WHERE id = ?"); +$stmt->execute([$emote_id]); +if ($stmt->rowCount() == 0) { + generate_alert("/emotes", "Emote not found", 404); + exit; +} +$emote = $stmt->fetch(PDO::FETCH_ASSOC); + +$user_id = $_SESSION["user_id"]; +$emote_set_id = $_POST["emote_set_id"]; + +// checking emote set +$stmt = $db->prepare("SELECT id FROM acquired_emote_sets WHERE emote_set_id = ? AND user_id = ?"); +$stmt->execute([$emote_set_id, $user_id]); + +if ($stmt->rowCount() == 0) { + generate_alert("/404.php", "You don't own emote set ID $emote_set_id", 403); + exit; +} + +// inserting emote +$stmt = $db->prepare("SELECT id FROM emote_set_contents WHERE emote_set_id = ? AND emote_id = ?"); +$stmt->execute([$emote_set_id, $emote_id]); + +$action = $_POST["action"]; +$payload = [ + "emote" => $emote, + "emoteset" => $_SESSION["user_active_emote_set"] +]; + +switch ($action) { + case "add": { + if ($stmt->rowCount() != 0) { + generate_alert("/emotes?id=$emote_id", "This emote has been already added!"); + exit; + } + + $stmt = $db->prepare("INSERT INTO emote_set_contents(emote_set_id, emote_id, added_by) VALUES (?, ?, ?)"); + $stmt->execute([$emote_set_id, $emote_id, $user_id]); + + if (ACCOUNT_LOG_ACTIONS) { + $db->prepare("INSERT INTO actions(user_id, action_type, action_payload) VALUES (?, ?, ?)") + ->execute([$user_id, "EMOTESET_ADD", json_encode($payload)]); + } + + $db = null; + + generate_alert("/emotes?id=$emote_id", "This emote has been added to your set. Enjoy!", 200); + break; + } + case "remove": { + if ($row = $stmt->fetch()) { + $stmt = $db->prepare("DELETE FROM emote_set_contents WHERE id = ?"); + $stmt->execute([$row["id"]]); + } else { + generate_alert("/emotes?id=$emote_id", "This emote wasn't added!"); + $db = null; + exit; + } + + if (ACCOUNT_LOG_ACTIONS) { + $db->prepare("INSERT INTO actions(user_id, action_type, action_payload) VALUES (?, ?, ?)") + ->execute([$user_id, "EMOTESET_REMOVE", json_encode($payload)]); + } + + $db = null; + + generate_alert("/emotes?id=$emote_id", "This emote has been removed from your set.", 200); + break; + } + case "alias": { + if (!isset($_POST["value"])) { + generate_alert("/emotes?id=$emote_id", "No value field"); + exit; + } + + $value = str_safe($_POST["value"], EMOTE_NAME_MAX_LENGTH); + + $stmt = $db->prepare("SELECT esc.code AS alias_code, e.code FROM emote_set_contents esc + INNER JOIN emotes e ON e.id = esc.emote_id + WHERE esc.emote_set_id = ? AND esc.emote_id = ?"); + $stmt->execute([$emote_set_id, $emote_id]); + + if (empty($value)) { + $value = null; + + if ($row = $stmt->fetch()) { + $payload["emote"]["original_code"] = $row["alias_code"]; + $payload["emote"]["code"] = $row["code"]; + } + } else { + $row = $stmt->fetch(); + $payload["emote"]["original_code"] = $row["alias_code"] ?? $row["code"]; + $payload["emote"]["code"] = $value; + } + + $stmt = $db->prepare("UPDATE emote_set_contents SET code = ? WHERE emote_set_id = ? AND emote_id = ?"); + $stmt->execute([$value, $emote_set_id, $emote_id]); + + if (ACCOUNT_LOG_ACTIONS) { + $db->prepare("INSERT INTO actions(user_id, action_type, action_payload) VALUES (?, ?, ?)") + ->execute([$user_id, "EMOTESET_ALIAS", json_encode($payload)]); + } + + $db = null; + + generate_alert("/emotes?id=$emote_id", "Updated emote name!", 200); + break; + } + default: { + generate_alert("/emotes?id=$emote_id", "Unknown action"); + break; + } +}
\ No newline at end of file diff --git a/emotes/upload.php b/emotes/upload.php new file mode 100644 index 0000000..644e4b6 --- /dev/null +++ b/emotes/upload.php @@ -0,0 +1,552 @@ +<?php +include "../../src/accounts.php"; +include_once "../../src/config.php"; +include_once "../../src/alert.php"; +include_once "../../src/captcha.php"; + +if (!EMOTE_UPLOAD) { + generate_alert("/404.php", "Emote upload is disabled", 403); + exit; +} + +authorize_user(); + +if (!ANONYMOUS_UPLOAD && isset($_SESSION["user_role"]) && !$_SESSION["user_role"]["permission_upload"]) { + generate_alert("/404.php", "Not enough permissions", 403); + exit; +} + +$uploaded_by = null; +$uploader_name = ANONYMOUS_DEFAULT_NAME; + +if (isset($_SESSION["user_role"]) && $_SESSION["user_role"]["permission_upload"]) { + $uploaded_by = $_SESSION["user_id"] ?? null; + $uploader_name = $_SESSION["user_name"] ?? ANONYMOUS_DEFAULT_NAME; +} + +$db = new PDO(DB_URL, DB_USER, DB_PASS); + +function abort_upload(string $path, PDO $db, string $id) +{ + $stmt = $db->prepare("DELETE FROM emotes WHERE id = ?"); + $stmt->execute([$id]); + $db = null; + + array_map("unlink", glob("$path/*.*")); + rmdir($path); +} + +include "../../src/utils.php"; +include "../../src/images.php"; + +$max_width = EMOTE_MAX_SIZE[0]; +$max_height = EMOTE_MAX_SIZE[1]; + +if ($_SERVER['REQUEST_METHOD'] != "POST") { + include "../../src/partials.php"; + + echo '' ?> + <html> + + <head> + <title>Upload an emote - <?php echo INSTANCE_NAME ?></title> + <link rel="stylesheet" href="/static/style.css"> + <link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon"> + </head> + + <body> + <div class="container"> + <div class="wrapper"> + <?php html_navigation_bar() ?> + <?php display_alert() ?> + + <section class="content row"> + <div class="column small-gap"> + <section class="box"> + <div class="box navtab"> + <div> + <b>Upload a new emote</b> + <p style="font-size:8px;">You can just upload, btw. Anything you want.</p> + </div> + </div> + <div class="box content"> + <form action="/emotes/upload.php" method="POST" enctype="multipart/form-data"> + <h3>Image<span style="color:red;">*</span></h3> + + <input type="file" name="file" id="form-file" accept=".gif,.jpg,.jpeg,.png,.webp" + required> + + <div id="form-manual-files" style="display:none;"> + <input type="file" name="file-1x" id="form-file-1x" + accept=".gif,.jpg,.jpeg,.png,.webp"> + <label class="inline" + for="file-1x"><?php echo sprintf("%dx%d", EMOTE_MAX_SIZE[0] / 4, EMOTE_MAX_SIZE[1] / 4) ?></label> + <input type="file" name="file-2x" id="form-file-2x" + accept=".gif,.jpg,.jpeg,.png,.webp"> + <label class="inline" + for="file-2x"><?php echo sprintf("%dx%d", EMOTE_MAX_SIZE[0] / 2, EMOTE_MAX_SIZE[1] / 2) ?></label> + <input type="file" name="file-3x" id="form-file-3x" + accept=".gif,.jpg,.jpeg,.png,.webp"> + <label class="inline" + for="file-3x"><?php echo sprintf("%dx%d", EMOTE_MAX_SIZE[0], EMOTE_MAX_SIZE[1]) ?></label> + </div> + + <div> + <label for="manual" class="inline">Manual resize</label> + <input type="checkbox" name="manual" value="1" onchange="display_manual_resize()"> + </div> + + <h3>Emote name<span style="color:red;">*</span></h3> + <input type="text" name="code" id="code" required> + + <div> + <label for="visibility" class="inline">Emote visibility: </label> + <select name="visibility" id="form-visibility"> + <option value="1">Public</option> + <option value="0">Unlisted</option> + </select><br> + <p id="form-visibility-description" style="font-size: 10px;">test</p> + </div> + + <label for="notes">Approval notes</label> + <textarea name="notes" id="form-notes"></textarea> + + <table class="vertical left font-weight-normal"> + <tr> + <th>Emote source:</th> + <td class="flex"><input class="grow" name="source" id="form-source"></input> + </td> + </tr> + <?php if (TAGS_ENABLE && TAGS_MAX_COUNT != 0): ?> + <tr> + <th>Tags <span class="font-small" style="cursor: help;" title="<?php + echo 'Tags are used for fast search. '; + if (TAGS_MAX_COUNT > 0) { + echo 'You can use ' . TAGS_MAX_COUNT . ' tags. '; + } + echo 'They are space-separated o algo.'; + ?>">[?]</span>: + </th> + <td class="flex"><input class="grow" name="tags" id="form-tags"></input></td> + </tr> + <?php endif; ?> + </table> + + <div> + <label for="tos" class="inline">Do you accept <a href="/rules.php" + target="_BLANK">the + rules</a>?<span style="color:red;">*</span></label> + <input type="checkbox" name="tos" value="1" required> + </div> + + <button type="submit" id="upload-button">Upload as + <?php echo $uploader_name ?></button> + </form> + </div> + </section> + + <?php + if (CAPTCHA_ENABLE && (CAPTCHA_FORCE_USERS || !isset($_SESSION["user_id"]))) { + html_captcha_form(); + } + ?> + </div> + + <div class="column small-gap grow" id="emote-showcase" style="display: none;"> + <!-- Emote Preview --> + <section class="box"> + <div class="box navtab"> + Emote Preview - <span id="emote-name"><i>Empty</i></span> + </div> + <div class="box content"> + <div class="emote-showcase items-bottom"> + <div class="emote-image column items-center small-gap"> + <img src="" alt="" class="emote-image-1x"> + <p class="size font-small"></p> + </div> + <div class="emote-image column items-center small-gap"> + <img src="" alt="" class="emote-image-2x"> + <p class="size font-small"></p> + </div> + <div class="emote-image column items-center small-gap"> + <img src="" alt="" class="emote-image-3x"> + <p class="size font-small"></p> + </div> + </div> + <p style="font-size: 12px;">The result may differ.</p> + </div> + </section> + + <!-- Chat Preview --> + <section class="box"> + <div class="box navtab"> + Chat Preview + </div> + <div class="box content no-gap column chat rounded"> + <?php + $stmt = $db->query("SELECT u.username, + CASE + WHEN ub.badge_id IS NOT NULL THEN ub.badge_id + WHEN r.badge_id IS NOT NULL THEN r.badge_id + ELSE NULL + END AS badge_id + FROM users u + LEFT JOIN user_badges ub ON ub.user_id = u.id + LEFT JOIN role_assigns ra ON ra.user_id = u.id + LEFT JOIN roles r ON r.id = ra.role_id + ORDER BY RAND() LIMIT 3 + "); + + while ($row = $stmt->fetch()) { + echo '<div class="row small-gap items-center chat-message">'; + + if ($row["badge_id"]) { + echo '<img src="/static/userdata/badges/' . $row["badge_id"] . '/1x.webp" alt="" title="" /> '; + } + + echo '<span style="color: rgb(' . random_int(128, 255) . ', ' . random_int(128, 255) . ', ' . random_int(128, 255) . ')">'; + echo $row["username"]; + echo ': </span>'; + + echo '<img src="" alt="" class="emote-image-1x">'; + + echo '</div>'; + } + ?> + </div> + </section> + </div> + </section> + </div> + </div> + </body> + + <script> + const max_width = <?php echo EMOTE_MAX_SIZE[0] ?>; + const max_height = <?php echo EMOTE_MAX_SIZE[1] ?>; + + const fileInput = document.getElementById("form-file"); + const showcase = document.getElementById("emote-showcase"); + const reader = new FileReader(); + + let manual = false; + + fileInput.addEventListener("change", (e) => { + if (manual) return; + + showcase.style.display = "flex"; + reader.readAsDataURL(e.target.files[0]); + reader.onload = (e) => { + const image = new Image(); + image.src = e.target.result; + image.onload = () => { + let m = 1; + + for (let i = 3; i > 0; i--) { + place_image(i, m, e, image); + m *= 2; + } + }; + }; + }); + + const code = document.getElementById("code"); + + code.addEventListener("input", (e) => { + const regex = <?php echo EMOTE_NAME_REGEX ?>; + + if (regex.test(e.target.value) && e.target.value.length <= <?php echo EMOTE_NAME_MAX_LENGTH ?>) { + validCode = e.target.value; + } else { + e.target.value = validCode; + } + + document.getElementById("emote-name").innerHTML = e.target.value ? e.target.value : "<i>Empty</i>"; + }); + + const visibility = document.getElementById("form-visibility"); + visibility.addEventListener("change", (e) => { + set_form_visibility_description(visibility.value); + }); + + function set_form_visibility_description(visibility) { + const p = document.getElementById("form-visibility-description"); + + if (visibility == 1) { + p.innerHTML = "Emote won't appear on the public list until it passes a moderator's review. It still can be added to chats."; + } else { + p.innerHTML = "Emote doesn't appear on the public list and won't be subject to moderation checks. It still can be added to chats."; + } + } + + set_form_visibility_description(visibility.value); + + // Manual resize + function display_manual_resize() { + const manual_files = document.getElementById("form-manual-files"); + + // resetting previous values + const files = document.querySelectorAll("input[type=file]"); + + for (let file of files) { + file.value = null; + file.removeAttribute("required"); + } + + const fileImages = document.querySelectorAll(".emote-image img"); + + for (let file of fileImages) { + file.setAttribute("src", ""); + file.setAttribute("width", "0"); + file.setAttribute("height", "0"); + } + + const fileSizes = document.querySelectorAll(".emote-image .size"); + + for (let file of fileImages) { + file.innerHTML = ""; + } + + manual = !manual; + + if (manual) { + manual_files.style.display = "block"; + fileInput.style.display = "none"; + const elements = document.querySelectorAll("#form-manual-files input[type=file]"); + for (let elem of elements) { + elem.setAttribute("required", "true"); + } + } else { + manual_files.style.display = "none"; + fileInput.style.display = "block"; + fileInput.setAttribute("required", "true"); + } + + showcase.style.display = "none"; + } + + document.getElementById("form-file-1x").addEventListener("change", (e) => { + showcase.style.display = "flex"; + place_image(1, 4, e, null); + }); + + document.getElementById("form-file-2x").addEventListener("change", (e) => { + showcase.style.display = "flex"; + place_image(2, 2, e, null); + }); + + document.getElementById("form-file-3x").addEventListener("change", (e) => { + showcase.style.display = "flex"; + place_image(3, 1, e, null); + }); + + function place_image(image_index, multiplier, e, image) { + let ee = e; + + if (image == null) { + reader.readAsDataURL(e.target.files[0]); + reader.onload = (e) => { + const image = new Image(); + image.src = e.target.result; + image.onload = () => { + insert_image(image_index, multiplier, e, image); + }; + } + } else { + insert_image(image_index, multiplier, e, image); + } + + function insert_image(i, m, e, image) { + const max_w = max_width / multiplier; + const max_h = max_height / multiplier; + + const parentId = `.emote-image-${image_index}x`; + const imgs = document.querySelectorAll(parentId); + + for (const img of imgs) { + img.setAttribute("src", e.target.result); + + let ratio = Math.min(max_w / image.width, max_h / image.height); + + img.setAttribute("width", Math.floor(image.width * ratio)); + img.setAttribute("height", Math.floor(image.height * ratio)); + + const sizeElement = document.querySelector(`.emote-image:has(${parentId}) .size`); + sizeElement.innerHTML = `${img.getAttribute("width")}x${img.getAttribute("height")}`; + } + } + } + </script> + + </html> + + <?php + exit; +} + +if (!CLIENT_REQUIRES_JSON && CAPTCHA_ENABLE && !isset($_SESSION["captcha_solved"])) { + generate_alert("/404.php", "You haven't solved captcha yet.", 403); + exit; +} + +$is_manual = intval($_POST["manual"] ?? "0") == 1; + +if ($is_manual && !isset($_FILES["file-1x"], $_FILES["file-2x"], $_FILES["file-3x"])) { + generate_alert("/emotes/upload.php", "No files set"); + exit; +} + +if (!$is_manual && !isset($_FILES["file"])) { + generate_alert("/emotes/upload.php", "No file set"); + exit; +} + +$code = str_safe($_POST["code"] ?? "", EMOTE_NAME_MAX_LENGTH); + +if ($code == "" || !preg_match(EMOTE_NAME_REGEX, $code)) { + generate_alert("/emotes/upload.php", "Invalid code"); + exit; +} + +$notes = str_safe($_POST["notes"] ?? "", EMOTE_COMMENT_MAX_LENGTH); +if (empty($notes)) { + $notes = null; +} + +$source = str_safe($_POST["source"] ?? "", null); +if (empty($source)) { + $source = null; +} + +$visibility = clamp(intval($_POST["visibility"], EMOTE_VISIBILITY_DEFAULT), 0, 2); + +if (MOD_EMOTES_APPROVE && $visibility == 1 && EMOTE_VISIBILITY_DEFAULT != 1) { + $visibility = 2; +} + +// creating a new emote record +$id = bin2hex(random_bytes(16)); +$stmt = $db->prepare("INSERT INTO emotes(id, code, notes, source, uploaded_by, visibility) VALUES (?, ?, ?, ?, ?, ?)"); +$stmt->execute([$id, $code, $notes, $source, $uploaded_by, $visibility]); + +$path = "../static/userdata/emotes/$id"; + +if (!is_dir($path)) { + mkdir($path, 0777, true); +} + +if ($is_manual) { + $image_1x = $_FILES["file-1x"]; + $image_2x = $_FILES["file-2x"]; + $image_3x = $_FILES["file-3x"]; + + $file_1x = does_file_meet_requirements($image_1x["tmp_name"], $max_width / 4, $max_height / 4); + $file_2x = does_file_meet_requirements($image_2x["tmp_name"], $max_width / 2, $max_height / 2); + $file_3x = does_file_meet_requirements($image_3x["tmp_name"], $max_width, $max_height); + + if (!$file_1x[0] || !$file_2x[0] || !$file_3x[0]) { + generate_alert("/emotes/upload.php", "Files don't meet requirements"); + abort_upload($path, $db, $id); + exit; + } + + if ( + !move_uploaded_file($image_1x["tmp_name"], "$path/1x.$file_1x[1]") || + !move_uploaded_file($image_2x["tmp_name"], "$path/2x.$file_2x[1]") || + !move_uploaded_file($image_3x["tmp_name"], "$path/3x.$file_3x[1]") + ) { + generate_alert("/emotes/upload.php", "Failed to move the uploaded files"); + abort_upload($path, $db, $id); + exit; + } +} else { + $image = $_FILES["file"]; + // resizing the image + if ($err = create_image_bundle($image["tmp_name"], $path, $max_width, $max_height)) { + generate_alert("/emotes/upload.php", "Error occurred while processing images ($err)", 500); + abort_upload($path, $db, $id); + exit; + } + + if (EMOTE_STORE_ORIGINAL) { + $ext = get_file_extension($image["tmp_name"]) ?? ""; + move_uploaded_file($image["tmp_name"], "$path/original.$ext"); + } +} + +$tags = str_safe($_POST["tags"] ?? "", null); +$tags_processed = []; + +if (!empty($tags) && TAGS_ENABLE) { + $tags = explode(" ", $tags); + + $count = 0; + + foreach ($tags as $tag) { + if (TAGS_MAX_COUNT > 0 && $count >= TAGS_MAX_COUNT) { + break; + } + + if (!preg_match(TAGS_CODE_REGEX, $tag)) { + continue; + } + + $tag_id = null; + + $stmt = $db->prepare("SELECT id FROM tags WHERE code = ?"); + $stmt->execute([$tag]); + + if ($row = $stmt->fetch()) { + $tag_id = $row["id"]; + } else { + $tag_id = bin2hex(random_bytes(16)); + $db->prepare("INSERT INTO tags(id, code) VALUES (?, ?)")->execute([$tag_id, $tag]); + } + + $db->prepare("INSERT INTO tag_assigns(tag_id, emote_id) VALUES (?, ?)")->execute([$tag_id, $id]); + + $count++; + array_push($tags_processed, $tag); + } +} + +$emote_data = [ + "id" => $id, + "code" => $code, + "visibility" => $visibility, + "uploaded_by" => match ($uploaded_by == null) { + true => null, + false => [ + "id" => $uploaded_by, + "username" => $uploader_name + ] + }, + "notes" => $notes, + "source" => $source, + "tags" => $tags_processed +]; + +if (ACCOUNT_LOG_ACTIONS && $uploaded_by != null) { + $db->prepare("INSERT INTO actions(user_id, action_type, action_payload) VALUES (?, ?, ?)") + ->execute([ + $uploaded_by, + "EMOTE_CREATE", + json_encode([ + "emote" => $emote_data + ]) + ]); +} + + +$db = null; + +if (CLIENT_REQUIRES_JSON) { + json_response([ + "status_code" => 201, + "message" => null, + "data" => $emote_data + ], 201); + exit; +} + +header("Location: /emotes?id=$id", true, 307);
\ No newline at end of file |
