diff --git a/app/Models/Comment.php b/app/Models/Comment.php
index 5a356da..40e9c96 100644
--- a/app/Models/Comment.php
+++ b/app/Models/Comment.php
@@ -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. Если текст не UTF‑8, конвертируем из 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 гарантируют корректную работу с 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 = '
' . $html . '
';
+
+ $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 '
' . Date::zmdate($this->c['posted_at']) . '
-
Цитировать
- ·
Ссылка
';
@@ -73,19 +188,31 @@ class Comment
$commclass = '';
}
echo '
- Фото: ' . Photo::fetchAll($this->c['user_id']) . ' ' . $admintype . '
- ' . preg_replace("~(?:[\p{M}]{1})([\p{M}])+?~uis", "", htmlspecialchars($this->c['body'])) . '
- ';
- if ($content['filetype'] === 'img') {
- echo '';
- }
- if ($content['filetype'] === 'video') {
- echo '';
- }
- echo '
+ Фото: ' . Photo::fetchAll($this->c['user_id']) . ' ' . $admintype . '
'; ?>
+
+ processContent($this->c['body']);
+
+ // Шаг 4: Вывод без дополнительного экранирования
+ echo '
' . $processedText . '
';
+
+ // ========== Вспомогательные методы ==========
+
+
+ ?>
+
';
+ }
+ if ($content['filetype'] === 'video') {
+ echo '';
+ }
+ echo '
';
- }
-}
+ }
+ }
diff --git a/app/Models/User.php b/app/Models/User.php
index a0115d4..1d4b440 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -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');
+ }
}
\ No newline at end of file
- c['user_id'] === Auth::userid()) { ?> - Редактировать
- Удалить - + + = $pinc ?>
+ c['user_id'] === Auth::userid()) { ?> + Редактировать
+ Удалить +