update models

This commit is contained in:
themohooks 2025-05-25 15:04:58 +03:00
parent 39193c88c0
commit 9a88345057
2 changed files with 167 additions and 31 deletions

View file

@ -2,7 +2,7 @@
namespace App\Models;
use \App\Services\{DB, Date, Auth};
use \App\Services\{DB, Date, Auth, Emoji, Word};
use \App\Models\{User, Photo, Vote};
class Comment
@ -24,6 +24,123 @@ class Comment
$content = json_decode($this->c['content'], true);
return $content[$table];
}
private function processContent($rawText)
{
// 1. Обработка упоминаний и смайлов
$withTags = Emoji::parseSmileys(Word::processMentions($rawText));
// 2. Селективное экранирование
$safeContent = $this->selectiveHtmlEscape($withTags);
// 3. Обрезка контента
return $this->truncateContent($safeContent, 200);
}
private function selectiveHtmlEscape(string $html): string
{
// 0. Если текст не UTF8, конвертируем из CP1251
if (!mb_check_encoding($html, 'UTF-8')) {
$html = mb_convert_encoding($html, 'UTF-8', 'CP1251');
}
// 1. Разбиваем на «теги» и «текст», сохраняя теги
$parts = preg_split('/(<[^>]+>)/u', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($parts as &$part) {
// 2. Тег — пропускаем
if (preg_match('/^<[^>]+>$/u', $part)) {
continue;
}
// 3. Текст — сначала декодируем все сущности, потом экранируем спецсимволы
// ENT_QUOTES|ENT_HTML5 и false у double_encode гарантируют корректную работу с &nbsp; etc.
$decoded = html_entity_decode($part, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$part = htmlspecialchars($decoded, ENT_QUOTES | ENT_HTML5, 'UTF-8', false);
}
unset($part);
// 4. Собираем обратно
return implode('', $parts);
}
private function truncateContent(string $html, int $maxLength): string
{
$dom = new \DOMDocument('1.0', 'UTF-8');
libxml_use_internal_errors(true);
$wrapped = '<?xml encoding="UTF-8"><div>' . $html . '</div>';
$dom->loadHTML($wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$xpath = new \DOMXPath($dom);
$node = $xpath->query('//div')->item(0);
$this->truncateNode($node, $maxLength);
return $dom->saveHTML($node);
}
private function truncateNode(\DOMNode $node, &$remaining)
{
if ($remaining <= 0) return;
foreach ($node->childNodes as $child) {
if ($child instanceof \DOMText) {
$text = $child->nodeValue;
$visible = mb_substr($text, 0, $remaining);
$hidden = mb_substr($text, $remaining);
if ($remaining < mb_strlen($text)) {
$child->nodeValue = $visible;
$remaining = 0;
// Создаём элемент для скрытой части
$hiddenNode = $child->ownerDocument->createElement('span');
$hiddenNode->setAttribute('class', 'hidden-text');
$hiddenTextNode = $child->ownerDocument->createTextNode($hidden);
$hiddenNode->appendChild($hiddenTextNode);
// Вставляем hiddenNode после текущего текстового узла
$parent = $child->parentNode;
if ($parent) {
if ($child->nextSibling) {
$parent->insertBefore($hiddenNode, $child->nextSibling);
} else {
$parent->appendChild($hiddenNode);
}
}
// Создаём кнопку "показать больше"
$button = $child->ownerDocument->createElement('a');
$buttonText = $child->ownerDocument->createTextNode('показать больше');
$button->appendChild($buttonText);
$button->setAttribute('class', 'toggle-message');
if ($parent) {
$parent->appendChild($button);
}
break;
}
$remaining -= mb_strlen($text);
} else {
$this->truncateNode($child, $remaining);
}
}
}
public function i()
{
$user = new User($this->c['user_id']);
@ -39,8 +156,6 @@ class Comment
echo '
<div style="float:right; text-align:right" class="sm">
<span class="message_date">' . Date::zmdate($this->c['posted_at']) . '</span><br>
<a href="#" class="quoteLink dot">Цитировать</a>
·
<a href="#' . $this->c['id'] . '" class="cmLink dot">Ссылка</a>
';
@ -73,19 +188,31 @@ class Comment
$commclass = '';
}
echo '</span></div>
<div class="rank">Фото: ' . Photo::fetchAll($this->c['user_id']) . ' ' . $admintype . '</div>
<div class="message-text">' . preg_replace("~(?:[\p{M}]{1})([\p{M}])+?~uis", "", htmlspecialchars($this->c['body'])) . '</div>
';
if ($content['filetype'] === 'img') {
echo '<div class="message-text"><img src="'.$content['src'].'" width="250"></div>';
}
if ($content['filetype'] === 'video') {
echo '<div class="message-text"><video controls src="'.$content['src'].'" width="250"></div>';
}
echo '
<div class="rank">Фото: ' . Photo::fetchAll($this->c['user_id']) . ' ' . $admintype . '</div>'; ?>
<div class="message-text">
<?php
// Правильный порядок:
$processedText = $this->processContent($this->c['body']);
// Шаг 4: Вывод без дополнительного экранирования
echo '<div class="message-text">' . $processedText . '</div>';
// ========== Вспомогательные методы ==========
?>
</div> <?php
if ($content['filetype'] === 'img') {
echo '<div class="message-text"><img src="' . $content['src'] . '" width="250"></div>';
}
if ($content['filetype'] === 'video') {
echo '<div class="message-text"><video controls src="' . $content['src'] . '" width="250"></div>';
}
echo '
<div class="comment-votes-block">
';
echo '<style>
echo '<style>
.dropdown {
position: relative;
display: inline-block;
@ -105,28 +232,28 @@ class Comment
display: block;
}
</style>';
if ($this->c['user_id'] === Auth::userid() || $photo->i('user_id') === Auth::userid()) {
echo '
if ($this->c['user_id'] === Auth::userid() || $photo->i('user_id') === Auth::userid()) {
echo '
<div class="dropdown">
<a style="color: #000" class="compl" href="#">...</a>
<div class="dropdown-content">';
?>
<a href="#" onclick="pinComment(<?= $this->c['id'] ?>); return false;"><?=$pinc?></a><br>
<?php
if ($this->c['user_id'] === Auth::userid()) { ?>
<a style="margin-bottom: 10px;" href="#" onclick="createModal(<?= $this->c['id'] ?>, 'EDIT_COMMENT', '<?= htmlspecialchars($this->c['body']) ?>', 'modaledit<?= $this->c['id'] ?>'); return false;">Редактировать</a><br>
<a href="#" onclick="createModal(<?= $this->c['id'] ?>, 'DELETE_COMMENT', '', 'modaldel<?= $this->c['id'] ?>'); return false;">Удалить</a>
<?php }
echo '
?>
<a href="#" onclick="pinComment(<?= $this->c['id'] ?>); return false;"><?= $pinc ?></a><br>
<?php
if ($this->c['user_id'] === Auth::userid()) { ?>
<a style="margin-bottom: 10px;" href="#" onclick="createModal(<?= $this->c['id'] ?>, 'EDIT_COMMENT', '<?= htmlspecialchars($this->c['body']) ?>', 'modaledit<?= $this->c['id'] ?>'); return false;">Редактировать</a><br>
<a href="#" onclick="createModal(<?= $this->c['id'] ?>, 'DELETE_COMMENT', '', 'modaldel<?= $this->c['id'] ?>'); return false;">Удалить</a>
<?php }
echo '
</div>
</div>
';
}
echo '
}
echo '
<div class="wvote" wid="' . $this->c['id'] . '">
<a href="#" vote="1" class="w-btn s2"><span>+</span></a>
@ -139,5 +266,5 @@ class Comment
</div>
</div>
</div>';
}
}
}
}

View file

@ -15,5 +15,14 @@ class User {
$content = json_decode(self::i('content'), true);
return $content[$table];
}
public function getPhotoUrl(): string
{
return $this->i('photourl');
}
public function getId(): int
{
return (int)$this->i('user_id');
}
}