Готово. Полный `tags.php` — с рандомной формой при каждом рендере (кеш хранит только частоты и формы, а выбор слова происходит при каждом выводе, так что F5 даёт новые слова). $endLen + 2) { if (mb_substr($word, -$endLen, null, 'UTF-8') === $end) { return mb_substr($word, 0, -$endLen, 'UTF-8'); } } } return $word; } } // ============================================================ // 2. Стоп-слова // ============================================================ if (!function_exists('ruStopWords')) { function ruStopWords() { static $words = null; if ($words !== null) return $words; $words = array_flip([ 'в','во','на','за','из','к','ко','с','со','у','о','об','обо','от','ото', 'по','под','подо','над','надо','при','про','для','без','безо','через', 'между','перед','передо','около','возле','вокруг','мимо','сквозь','среди', 'и','а','но','да','или','либо','то','же','бы','чтобы','что','как','когда', 'если','потому','поэтому','так','тоже','также','ни','не','нет', 'я','ты','он','она','оно','мы','вы','они','мне','тебе','ему','ей','нам', 'вам','им','меня','тебя','его','её','нас','вас','их','себя', 'мой','моя','моё','мои','твой','твоя','твоё','твои','наш','наша','наше', 'наши','ваш','ваша','ваше','ваши','свой','своя','своё','свои', 'это','этот','эта','эти','тот','та','те','такой','такая','такое', 'такие','весь','вся','всё','все','сам','сама','само','сами', 'тут','там','здесь','где','куда','откуда','тогда','сейчас','потом', 'очень','ещё','уже','только','даже','ведь','вот','ну','ли','будто', 'можно','нужно','надо','есть','был','была','было','были','будет','будут', 'elvir','ru','http','https','www','com','html','php', ]); return $words; } } // ============================================================ // 3. Построение частотного словаря (с сохранением форм) // ============================================================ if (!function_exists('buildTagCloud')) { function buildTagCloud($pdo, $limit = 30) { $texts = []; try { $stmt = $pdo->query("SELECT message FROM messages"); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $texts[] = $row['message']; } } catch (PDOException $e) { /* таблицы может не быть */ } try { $stmt = $pdo->query("SELECT message FROM projects"); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $texts[] = $row['message']; } } catch (PDOException $e) { /* ... */ } if (empty($texts)) return []; $stop = ruStopWords(); $freq = []; // stem => count $forms = []; // stem => [форма => сколько раз встретилась] foreach ($texts as $raw) { $text = preg_replace('/\[[^\]]+\]/u', ' ', $raw); $text = preg_replace('/https?:\/\/\S+/u', ' ', $text); $text = strip_tags($text); $text = preg_replace('/[^а-яёА-ЯЁ\s]+/u', ' ', $text); $text = mb_strtolower($text, 'UTF-8'); $words = preg_split('/\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY); foreach ($words as $w) { if (mb_strlen($w, 'UTF-8') < 3) continue; if (isset($stop[$w])) continue; $stem = ruStem($w); if (mb_strlen($stem, 'UTF-8') < 3) continue; if (!isset($freq[$stem])) { $freq[$stem] = 0; $forms[$stem] = []; } $freq[$stem]++; if (!isset($forms[$stem][$w])) $forms[$stem][$w] = 0; $forms[$stem][$w]++; } } arsort($freq); $top = array_slice($freq, 0, $limit, true); $result = []; foreach ($top as $stem => $count) { $result[$stem] = [ 'count' => $count, 'forms' => $forms[$stem], ]; } return $result; } } // ============================================================ // 4. Кеширование (хранит частоты и формы, рендер — каждый раз разный) // ============================================================ if (!function_exists('buildTagCloudCached')) { function buildTagCloudCached($pdo, $limit = 30, $ttl = 3600) { $cacheDir = $_SERVER['DOCUMENT_ROOT'] . '/cache'; $cacheFile = $cacheDir . '/tags.json'; if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $ttl)) { $data = json_decode(file_get_contents($cacheFile), true); if (is_array($data)) return $data; } $tags = buildTagCloud($pdo, $limit); if (!is_dir($cacheDir)) @mkdir($cacheDir, 0777, true); @file_put_contents($cacheFile, json_encode($tags, JSON_UNESCAPED_UNICODE)); return $tags; } } // ============================================================ // 5. Отрисовка облака (случайная форма при каждом вызове) // ============================================================ if (!function_exists('renderTagCloud')) { function renderTagCloud($tags) { if (empty($tags)) { return '