mirror of
https://github.com/Shadowss/TravianZ.git
synced 2026-09-16 15:17:17 +00:00
Add Global Chat Options
Add Global Chat Options 1. emoji 2. Poll 3. Delete message 4. Edit message
This commit is contained in:
@@ -12,6 +12,10 @@
|
||||
## Cerut de Catalin, 07.09.2026: chat general vizibil/scris de orice ##
|
||||
## jucator logat, cu moderare pentru Admin (access 9) si MH (access 8). ##
|
||||
## ##
|
||||
## Faza 2 (09.09.2026): stergere mesaj (mod), editare mesaj propriu (user), ##
|
||||
## sondaje (chat_global_polls / chat_global_poll_votes) si suport emoji ##
|
||||
## (chat_global trecut pe utf8mb4 - vezi struct.sql). ##
|
||||
## ##
|
||||
## License: TravianZ Project ##
|
||||
## Copyright: TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## URLs: https://travianz.org ##
|
||||
@@ -79,9 +83,10 @@ trait DatabaseGlobalChatQueries {
|
||||
* singur JOIN (nu o interogare per mesaj) - reflecta mereu alianta
|
||||
* curenta a jucatorului, nu una "inghetata" la momentul postarii.
|
||||
*/
|
||||
function getGlobalChatMessages($limit = 30, $sinceId = 0) {
|
||||
function getGlobalChatMessages($limit = 30, $sinceId = 0, $viewerUid = 0) {
|
||||
$limit = (int) $limit;
|
||||
$sinceId = (int) $sinceId;
|
||||
$viewerUid = (int) $viewerUid;
|
||||
|
||||
$where = $sinceId > 0 ? "WHERE c.id > $sinceId" : "";
|
||||
$order = $sinceId > 0 ? "ORDER BY c.id ASC" : "ORDER BY c.id DESC";
|
||||
@@ -90,7 +95,10 @@ trait DatabaseGlobalChatQueries {
|
||||
// browser pe mobil), la revenire nu vrem un SELECT nemarginit
|
||||
$limitSql = "LIMIT " . ($sinceId > 0 ? 200 : $limit);
|
||||
|
||||
$q = "SELECT c.id, c.id_user, c.date, c.msg, u.username, u.access, a.tag AS ally_tag, a.id AS ally_id
|
||||
// Faza 2: c.type/c.poll_id/c.deleted/c.edited - vezi editGlobalChatMessage(),
|
||||
// deleteGlobalChatMessage() si createGlobalChatPoll() mai jos
|
||||
$q = "SELECT c.id, c.id_user, c.date, c.msg, c.type, c.poll_id, c.deleted, c.edited,
|
||||
u.username, u.access, a.tag AS ally_tag, a.id AS ally_id
|
||||
FROM " . TB_PREFIX . "chat_global c
|
||||
LEFT JOIN " . TB_PREFIX . "users u ON u.id = c.id_user
|
||||
LEFT JOIN " . TB_PREFIX . "alidata a ON a.id = u.alliance AND u.alliance > 0
|
||||
@@ -106,9 +114,303 @@ trait DatabaseGlobalChatQueries {
|
||||
$rows = array_reverse($rows);
|
||||
}
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
if ((int) $row['deleted'] === 1) {
|
||||
// golim textul server-side (nu doar in client) - un mesaj sters de
|
||||
// moderator (de obicei vulgar) nu trebuie sa mai ajunga deloc, sub
|
||||
// nicio forma, in raspunsul JSON catre ceilalti clienti
|
||||
$row['msg'] = '';
|
||||
} elseif ($row['type'] === 'poll' && $row['poll_id']) {
|
||||
$row['poll'] = $this->getGlobalChatPollData($row['poll_id'], $viewerUid);
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Editeaza un mesaj propriu (text simplu, nu sondaje) - vezi
|
||||
* deleteGlobalChatMessage() pentru actiunea de moderare (mesajul altcuiva).
|
||||
* Fara limita de timp la editare (nu a fost ceruta); usor de adaugat o
|
||||
* fereastra (ex. doar primele 5 minute) daca se doreste ulterior.
|
||||
*
|
||||
* @return array ['ok'=>bool, 'reason'=>string|null]
|
||||
*/
|
||||
function editGlobalChatMessage($uid, $msgId, $newMsg) {
|
||||
$uid = (int) $uid;
|
||||
$msgId = (int) $msgId;
|
||||
$newMsg = trim((string) $newMsg);
|
||||
|
||||
if ($uid <= 0 || $msgId <= 0) {
|
||||
return ['ok' => false, 'reason' => 'invalid'];
|
||||
}
|
||||
|
||||
if ($newMsg === '') {
|
||||
return ['ok' => false, 'reason' => 'empty'];
|
||||
}
|
||||
|
||||
$newMsg = function_exists('mb_substr') ? mb_substr($newMsg, 0, 250) : substr($newMsg, 0, 250);
|
||||
|
||||
$row = mysqli_fetch_assoc($this->query(
|
||||
"SELECT id_user, type, deleted FROM " . TB_PREFIX . "chat_global WHERE id = $msgId"
|
||||
));
|
||||
|
||||
if (!$row) {
|
||||
return ['ok' => false, 'reason' => 'notfound'];
|
||||
}
|
||||
|
||||
// doar propriul mesaj, doar daca nu a fost deja sters de un moderator,
|
||||
// si doar mesaje text (ancora unui sondaj nu se editeaza ca text simplu)
|
||||
if ((int) $row['id_user'] !== $uid || (int) $row['deleted'] === 1 || $row['type'] !== 'text') {
|
||||
return ['ok' => false, 'reason' => 'forbidden'];
|
||||
}
|
||||
|
||||
list($emsg) = $this->escape_input($newMsg);
|
||||
$now = time();
|
||||
|
||||
$this->query(
|
||||
"UPDATE " . TB_PREFIX . "chat_global SET msg = '$emsg', edited = 1, updated_at = $now WHERE id = $msgId"
|
||||
);
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sterge (soft-delete) un mesaj din chat-ul general - actiune de moderare.
|
||||
* Textul e golit aici (nu doar ascuns in client), acelasi motiv ca la
|
||||
* golirea din getGlobalChatMessages() mai sus. Verificarea de rang (MH/
|
||||
* Admin, access minim) se face in ajax.php, la fel ca la mute/block - DAR
|
||||
* spre deosebire de mute/block, aici NU se compara rangul cu al autorului:
|
||||
* stergerea vizeaza continutul (un mesaj vulgar), nu persoana, deci orice
|
||||
* MH/Admin poate sterge orice mesaj, inclusiv al altui MH, ca sa poata
|
||||
* curata rapid chat-ul.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function deleteGlobalChatMessage($msgId) {
|
||||
$msgId = (int) $msgId;
|
||||
$now = time();
|
||||
|
||||
return $this->query(
|
||||
"UPDATE " . TB_PREFIX . "chat_global SET msg = '', deleted = 1, updated_at = $now WHERE id = $msgId AND deleted = 0"
|
||||
) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mesaje editate/sterse de la ultimul cec al clientului ($sinceTs, unix) -
|
||||
* necesar ca sa se poata actualiza "in loc" mesajele deja randate la un
|
||||
* client care are panoul de chat deschis de mai mult timp: poll-ul normal
|
||||
* de mesaje noi (getGlobalChatMessages, dupa id) nu ar mai prinde o
|
||||
* editare/stergere pe un mesaj vechi, deja afisat, cu id mai mic decat
|
||||
* ultimul id vazut de client.
|
||||
*
|
||||
* Include si datele proaspete de sondaj pentru randurile de tip 'poll'
|
||||
* (ex. cand cineva voteaza, votul nu schimba mesajul in sine, dar
|
||||
* voteGlobalChatPoll() atinge acest rand exact ca sa fie prins aici -
|
||||
* altfel doar cel care voteaza ar vedea rezultatul actualizat).
|
||||
*
|
||||
* @return array ['rows'=>array, 'now'=>int] - 'now' devine noul watermark pe client
|
||||
*/
|
||||
function getGlobalChatUpdates($sinceTs, $viewerUid = 0, $limit = 100) {
|
||||
$sinceTs = (int) $sinceTs;
|
||||
$viewerUid = (int) $viewerUid;
|
||||
$now = time();
|
||||
$limit = (int) $limit;
|
||||
|
||||
$rows = $this->mysqli_fetch_all($this->query(
|
||||
"SELECT id, msg, type, poll_id, deleted, edited FROM " . TB_PREFIX . "chat_global
|
||||
WHERE updated_at > $sinceTs AND updated_at <= $now
|
||||
ORDER BY updated_at ASC
|
||||
LIMIT $limit"
|
||||
));
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
if ((int) $row['deleted'] === 1) {
|
||||
$row['msg'] = '';
|
||||
} elseif ($row['type'] === 'poll' && $row['poll_id']) {
|
||||
$row['poll'] = $this->getGlobalChatPollData($row['poll_id'], $viewerUid);
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return ['rows' => $rows, 'now' => $now];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creeaza un sondaj in chat-ul general: un rand-ancora in chat_global
|
||||
* (type='poll', ca sa-si pastreze locul cronologic in flux de mesaje) +
|
||||
* randul cu intrebarea/optiunile in chat_global_polls. Reutilizeaza
|
||||
* acelasi mute-check ca postGlobalChatMessage (un user mutat nu poate
|
||||
* nici posta mesaje, nici crea sondaje).
|
||||
*
|
||||
* @param array $options 2-6 optiuni (string-uri) - restul sunt ignorate
|
||||
* @return array ['ok'=>bool, 'reason'=>string|null]
|
||||
*/
|
||||
function createGlobalChatPoll($uid, $question, $options) {
|
||||
$uid = (int) $uid;
|
||||
$question = trim((string) $question);
|
||||
$question = function_exists('mb_substr') ? mb_substr($question, 0, 200) : substr($question, 0, 200);
|
||||
|
||||
if ($uid <= 0 || $question === '') {
|
||||
return ['ok' => false, 'reason' => 'invalid'];
|
||||
}
|
||||
|
||||
$clean = [];
|
||||
foreach ((array) $options as $opt) {
|
||||
$opt = trim((string) $opt);
|
||||
if ($opt === '') {
|
||||
continue;
|
||||
}
|
||||
$clean[] = function_exists('mb_substr') ? mb_substr($opt, 0, 60) : substr($opt, 0, 60);
|
||||
if (count($clean) >= 6) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($clean) < 2) {
|
||||
return ['ok' => false, 'reason' => 'notenoughoptions'];
|
||||
}
|
||||
|
||||
$mutedUntil = $this->getGlobalChatMuteStatus($uid);
|
||||
if ($mutedUntil !== null) {
|
||||
return ['ok' => false, 'reason' => 'muted', 'mutedUntil' => $mutedUntil];
|
||||
}
|
||||
|
||||
list($euid) = $this->escape_input($uid);
|
||||
list($eq) = $this->escape_input($question);
|
||||
$now = time();
|
||||
|
||||
// 1) randul-ancora in chat_global (msg = intrebarea - fallback util daca
|
||||
// ceva citeste chat_global fara sa stie de chat_global_polls)
|
||||
$this->query(
|
||||
"INSERT INTO " . TB_PREFIX . "chat_global (id_user, date, msg, type) VALUES ($euid, $now, '$eq', 'poll')"
|
||||
);
|
||||
$chatId = mysqli_insert_id($this->dblink);
|
||||
|
||||
// 2) intrebarea + optiunile (JSON)
|
||||
list($eOptions) = $this->escape_input(json_encode(array_values($clean)));
|
||||
$this->query(
|
||||
"INSERT INTO " . TB_PREFIX . "chat_global_polls (chat_id, id_user, question, options, created)
|
||||
VALUES ($chatId, $euid, '$eq', '$eOptions', $now)"
|
||||
);
|
||||
$pollId = mysqli_insert_id($this->dblink);
|
||||
|
||||
// 3) leaga ancora de randul de sondaj
|
||||
$this->query(
|
||||
"UPDATE " . TB_PREFIX . "chat_global SET poll_id = $pollId WHERE id = $chatId"
|
||||
);
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* Inregistreaza votul unui user la un sondaj din chat-ul general. Userul
|
||||
* isi poate schimba optiunea (ON DUPLICATE KEY UPDATE) - nu exista un
|
||||
* "vot definitiv", nefiind cerut.
|
||||
*
|
||||
* @return array ['ok'=>bool, 'reason'=>string|null]
|
||||
*/
|
||||
function voteGlobalChatPoll($uid, $pollId, $optionIndex) {
|
||||
$uid = (int) $uid;
|
||||
$pollId = (int) $pollId;
|
||||
$optionIndex = (int) $optionIndex;
|
||||
|
||||
if ($uid <= 0 || $pollId <= 0 || $optionIndex < 0) {
|
||||
return ['ok' => false, 'reason' => 'invalid'];
|
||||
}
|
||||
|
||||
$poll = mysqli_fetch_assoc($this->query(
|
||||
"SELECT chat_id, options FROM " . TB_PREFIX . "chat_global_polls WHERE id = $pollId"
|
||||
));
|
||||
|
||||
if (!$poll) {
|
||||
return ['ok' => false, 'reason' => 'notfound'];
|
||||
}
|
||||
|
||||
$options = json_decode($poll['options'], true);
|
||||
if (!is_array($options) || !isset($options[$optionIndex])) {
|
||||
return ['ok' => false, 'reason' => 'invalid'];
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$this->query(
|
||||
"INSERT INTO " . TB_PREFIX . "chat_global_poll_votes (poll_id, id_user, option_index, voted_at)
|
||||
VALUES ($pollId, $uid, $optionIndex, $now)
|
||||
ON DUPLICATE KEY UPDATE option_index = $optionIndex, voted_at = $now"
|
||||
);
|
||||
|
||||
// atinge randul-ancora din chat_global ca votul sa fie prins de
|
||||
// getGlobalChatUpdates() (watermark-ul de editari/stergeri) - altfel
|
||||
// doar userul care a votat ar vedea rezultatul actualizat, ceilalti
|
||||
// clienti cu sondajul deja afisat ar ramane cu numaratoarea veche
|
||||
$chatId = (int) $poll['chat_id'];
|
||||
$this->query(
|
||||
"UPDATE " . TB_PREFIX . "chat_global SET updated_at = $now WHERE id = $chatId"
|
||||
);
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rezultatele unui sondaj + optiunea aleasa de $viewerUid (daca a votat).
|
||||
* Apelata per-rand din getGlobalChatMessages() pentru mesajele de tip
|
||||
* 'poll' - sondajele sunt rare fata de mesajele normale, deci interogarile
|
||||
* suplimentare (N+1) sunt neglijabile aici.
|
||||
*/
|
||||
function getGlobalChatPollData($pollId, $viewerUid) {
|
||||
$pollId = (int) $pollId;
|
||||
$viewerUid = (int) $viewerUid;
|
||||
|
||||
$poll = mysqli_fetch_assoc($this->query(
|
||||
"SELECT id, question, options FROM " . TB_PREFIX . "chat_global_polls WHERE id = $pollId"
|
||||
));
|
||||
|
||||
if (!$poll) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$options = json_decode($poll['options'], true);
|
||||
if (!is_array($options)) {
|
||||
$options = [];
|
||||
}
|
||||
|
||||
$counts = array_fill(0, count($options), 0);
|
||||
$voteRows = $this->mysqli_fetch_all($this->query(
|
||||
"SELECT option_index, COUNT(*) AS c FROM " . TB_PREFIX . "chat_global_poll_votes
|
||||
WHERE poll_id = $pollId GROUP BY option_index"
|
||||
));
|
||||
|
||||
$total = 0;
|
||||
foreach ($voteRows as $vr) {
|
||||
$idx = (int) $vr['option_index'];
|
||||
if (isset($counts[$idx])) {
|
||||
$counts[$idx] = (int) $vr['c'];
|
||||
}
|
||||
$total += (int) $vr['c'];
|
||||
}
|
||||
|
||||
$myVote = null;
|
||||
if ($viewerUid > 0) {
|
||||
$mv = mysqli_fetch_assoc($this->query(
|
||||
"SELECT option_index FROM " . TB_PREFIX . "chat_global_poll_votes
|
||||
WHERE poll_id = $pollId AND id_user = $viewerUid"
|
||||
));
|
||||
if ($mv) {
|
||||
$myVote = (int) $mv['option_index'];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int) $poll['id'],
|
||||
'question' => $poll['question'],
|
||||
'options' => $options,
|
||||
'counts' => $counts,
|
||||
'total' => $total,
|
||||
'myVote' => $myVote,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp-ul (unix) pana la care userul e mutat din chat-ul general,
|
||||
* sau null daca nu e mutat (inclusiv daca o mutare veche a expirat).
|
||||
|
||||
Reference in New Issue
Block a user