Просмотр файла app/helpers.php

Размер файла: 56.53Kb
  1. <?php
  2.  
  3. use App\Models\AdminAdvert;
  4. use App\Models\PaidAdvert;
  5. use App\Classes\{BBCode, Calendar, Metrika, CloudFlare};
  6. use App\Models\Antimat;
  7. use App\Models\Ban;
  8. use App\Models\Banhist;
  9. use App\Models\BlackList;
  10. use App\Models\Article;
  11. use App\Models\Item;
  12. use App\Models\Load;
  13. use App\Models\Chat;
  14. use App\Models\Counter;
  15. use App\Models\Down;
  16. use App\Models\Guestbook;
  17. use App\Models\Invite;
  18. use App\Models\Error;
  19. use App\Models\News;
  20. use App\Models\Notice;
  21. use App\Models\Offer;
  22. use App\Models\Online;
  23. use App\Models\Photo;
  24. use App\Models\Post;
  25. use App\Models\Advert;
  26. use App\Models\Setting;
  27. use App\Models\Sticker;
  28. use App\Models\Spam;
  29. use App\Models\Topic;
  30. use App\Models\User;
  31. use App\Models\Vote;
  32. use GuzzleHttp\Client;
  33. use Illuminate\Mail\Message;
  34. use Illuminate\Support\Collection;
  35. use Illuminate\Support\Facades\DB;
  36. use Illuminate\Database\Eloquent\Builder;
  37. use Illuminate\Pagination\LengthAwarePaginator;
  38. use Illuminate\Pagination\Paginator;
  39. use Illuminate\Support\Arr;
  40. use Illuminate\Support\Facades\Cache;
  41. use Illuminate\Support\Facades\Mail;
  42. use Illuminate\Support\HtmlString;
  43. use Illuminate\Support\Str;
  44. use Illuminate\Support\ViewErrorBag;
  45. use Intervention\Image\Constraint;
  46. use Intervention\Image\ImageManagerStatic as Image;
  47. use ReCaptcha\ReCaptcha;
  48.  
  49. const ROTOR_VERSION = '11.1';
  50. define('SITETIME', time());
  51.  
  52. /**
  53. * Форматирует вывод времени из секунд
  54. *
  55. * @param int $time секунды
  56. *
  57. * @return string Форматированный вывод
  58. */
  59. function makeTime(int $time): string
  60. {
  61. $format = $time < 3600 ? 'i:s' : 'H:i:s';
  62.  
  63. return gmdate($format, $time);
  64. }
  65.  
  66. /**
  67. * Форматирует время с учетом часовых поясов
  68. *
  69. * @param int|null $timestamp Секунды
  70. * @param string $format Формат времени
  71. * @param bool $original Формат без изменения
  72. *
  73. * @return string Форматированный вывод
  74. */
  75. function dateFixed(?int $timestamp, string $format = 'd.m.Y / H:i', bool $original = false): string
  76. {
  77. if (! is_numeric($timestamp)) {
  78. $timestamp = SITETIME;
  79. }
  80.  
  81. $shift = getUser('timezone') * 3600;
  82. $dateStamp = date($format, $timestamp + $shift);
  83.  
  84. if ($original) {
  85. return $dateStamp;
  86. }
  87.  
  88. $today = date('d.m.Y', SITETIME + $shift);
  89. $yesterday = date('d.m.Y', strtotime('-1 day', SITETIME + $shift));
  90.  
  91. $replaces = [
  92. $today => __('main.today'),
  93. $yesterday => __('main.yesterday'),
  94. 'January' => __('main.january'),
  95. 'February' => __('main.february'),
  96. 'March' => __('main.march'),
  97. 'April' => __('main.april'),
  98. 'May' => __('main.may'),
  99. 'June' => __('main.june'),
  100. 'July' => __('main.july'),
  101. 'August' => __('main.august'),
  102. 'September' => __('main.september'),
  103. 'October' => __('main.october'),
  104. 'November' => __('main.november'),
  105. 'December' => __('main.december'),
  106. ];
  107.  
  108. return strtr($dateStamp, $replaces);
  109. }
  110.  
  111. /**
  112. * Конвертирует строку в кодировку utf-8
  113. *
  114. * @param string $str строка
  115. *
  116. * @return string Конвертированная строка
  117. */
  118. function winToUtf(string $str): string
  119. {
  120. return mb_convert_encoding($str, 'utf-8', 'windows-1251');
  121. }
  122.  
  123. /**
  124. * Преобразует строку в нижний регистр
  125. *
  126. * @param string $str строка
  127. *
  128. * @return string Преобразованная строка
  129. */
  130. function utfLower(string $str): string
  131. {
  132. return mb_strtolower($str, 'utf-8');
  133. }
  134.  
  135. /**
  136. * Обрезает строку
  137. *
  138. * @param mixed $str Строка
  139. * @param int $start Начало позиции
  140. * @param int|null $length Конец позиции
  141. *
  142. * @return string Обрезанная строка
  143. */
  144. function utfSubstr(mixed $str, int $start, ?int $length = null): string
  145. {
  146. if (! $length) {
  147. $length = utfStrlen($str);
  148. }
  149.  
  150. return mb_substr((string) $str, $start, $length, 'utf-8');
  151. }
  152.  
  153. /**
  154. * Возвращает длину строки
  155. *
  156. * @param mixed $str строка
  157. *
  158. * @return int Длина строка
  159. */
  160. function utfStrlen($str): int
  161. {
  162. return mb_strlen($str, 'utf-8');
  163. }
  164.  
  165. /**
  166. * Является ли кодировка utf-8
  167. *
  168. * @param string $str строка
  169. *
  170. * @return bool
  171. */
  172. function isUtf(string $str): bool
  173. {
  174. return mb_check_encoding($str, 'utf-8');
  175. }
  176.  
  177. /**
  178. * Преобразует специальные символы в HTML-сущности
  179. *
  180. * @param mixed $string Строка или массив строк
  181. * @param bool $doubleEncode Преобразовывать существующие html-сущности
  182. *
  183. * @return array|string Обработанные данные
  184. */
  185. function check($string, bool $doubleEncode = true)
  186. {
  187. if (is_array($string)) {
  188. foreach ($string as $key => $val) {
  189. $string[$key] = check($val, $doubleEncode);
  190. }
  191. } else {
  192. $string = htmlspecialchars($string, ENT_QUOTES, 'UTF-8', $doubleEncode);
  193. $search = [chr(0), "\x00", "\x1A", chr(226) . chr(128) . chr(174)];
  194. $string = str_replace($search, [], $string);
  195. }
  196.  
  197. return $string;
  198. }
  199.  
  200. /**
  201. * Преобразует в положительное число
  202. *
  203. * @param int|string $num число
  204. *
  205. * @return int Обработанные данные
  206. */
  207. function int($num): int
  208. {
  209. return abs((int) $num);
  210. }
  211.  
  212. /**
  213. * Преобразует все элементы массива в int
  214. *
  215. * @param array|int|string $numbers массив или число
  216. *
  217. * @return array|null Обработанные данные
  218. */
  219. function intar($numbers): ?array
  220. {
  221. if ($numbers) {
  222. if (is_array($numbers)) {
  223. $numbers = array_map('intval', $numbers);
  224. } else {
  225. $numbers = [(int) $numbers];
  226. }
  227. }
  228.  
  229. return $numbers;
  230. }
  231.  
  232. /**
  233. * Возвращает размер в человеко читаемом формате
  234. *
  235. * @param int $bytes размер в байтах
  236. * @param int $precision кол. символов после запятой
  237. *
  238. * @return string Форматированный вывод размера
  239. */
  240. function formatSize(int $bytes, int $precision = 2): string
  241. {
  242. $units = ['B', 'Kb', 'Mb', 'Gb', 'Tb'];
  243. $pow = floor(($bytes ? log($bytes) : 0) / log(1000));
  244. $pow = min($pow, count($units) - 1);
  245.  
  246. $bytes /= (1 << (10 * $pow));
  247.  
  248. return round($bytes, $precision) . $units[$pow];
  249. }
  250.  
  251. /**
  252. * Возвращает размер файла человеко-читаемом формате
  253. *
  254. * @param string $file Путь к файлу
  255. *
  256. * @return string Размер в читаемом формате
  257. */
  258. function formatFileSize(string $file): string
  259. {
  260. if (file_exists($file) && is_file($file)) {
  261. return formatSize(filesize($file));
  262. }
  263.  
  264. return formatSize(0);
  265. }
  266.  
  267. /**
  268. * Возвращает время в человеко-читаемом формате
  269. *
  270. * @param int $time Кол. секунд timestamp
  271. * @param int $crumbs Кол. элементов
  272. *
  273. * @return string Время в читаемом формате
  274. */
  275. function formatTime(int $time, int $crumbs = 2): string
  276. {
  277. if ($time < 1) {
  278. return '0';
  279. }
  280.  
  281. $units = [
  282. __('main.plural_years') => 31536000,
  283. __('main.plural_months') => 2592000,
  284. __('main.plural_days') => 86400,
  285. __('main.plural_hours') => 3600,
  286. __('main.plural_minutes') => 60,
  287. __('main.plural_seconds') => 1,
  288. ];
  289.  
  290. $return = [];
  291.  
  292. foreach ($units as $unit => $seconds) {
  293. $format = floor($time / $seconds);
  294. $time %= $seconds;
  295.  
  296. if ($format >= 1) {
  297. $return[] = plural($format, $unit);
  298. }
  299. }
  300.  
  301. return implode(' ', array_slice($return, 0, $crumbs));
  302. }
  303.  
  304. /**
  305. * Очищает строку от мата по базе слов
  306. *
  307. * @param string|null $str строка
  308. *
  309. * @return string Обработанная строка
  310. */
  311. function antimat(?string $str): string
  312. {
  313. return Antimat::replace((string) $str);
  314. }
  315.  
  316. /**
  317. * Возвращает календарь
  318. *
  319. * @param int $time
  320. *
  321. * @return HtmlString календарь
  322. */
  323. function getCalendar(int $time = SITETIME): HtmlString
  324. {
  325. $calendar = new Calendar();
  326.  
  327. return new HtmlString($calendar->getCalendar($time));
  328. }
  329.  
  330. /**
  331. * Возвращает количество пользователей онлайн по типам
  332. *
  333. * @return array Массив данных
  334. */
  335. function statsOnline(): array
  336. {
  337. return Cache::remember('online', 60, static function () {
  338. $users = Online::query()
  339. ->select('user_id')
  340. ->distinct('user_id')
  341. ->whereNotNull('user_id')
  342. ->get();
  343.  
  344. $usersCount = $users->count();
  345. $guestsCount = Online::query()->whereNull('user_id')->count();
  346. $total = $usersCount + $guestsCount;
  347.  
  348. $metrika = new Metrika();
  349. $metrika->getCounter($usersCount + $guestsCount);
  350.  
  351. return [$usersCount, $guestsCount, $total, $users];
  352. });
  353. }
  354.  
  355. /**
  356. * Возвращает количество пользователей онлайн
  357. *
  358. * @return HtmlString|null
  359. */
  360. function showOnline(): ?HtmlString
  361. {
  362. if (setting('onlines')) {
  363. $online = statsOnline();
  364.  
  365. return new HtmlString(view('app/_online', compact('online')));
  366. }
  367.  
  368. return null;
  369. }
  370.  
  371. /**
  372. * Get online widget
  373. *
  374. * @return mixed
  375. */
  376. function onlineWidget(): HtmlString
  377. {
  378. $online = statsOnline();
  379.  
  380. return new HtmlString(view('widgets/_online', compact('online')));
  381. }
  382.  
  383. /**
  384. * Возвращает статистику посещений
  385. *
  386. * @return array Статистика посещений
  387. */
  388. function statsCounter(): array
  389. {
  390. return Cache::remember('counter', 30, static function () {
  391. $counter = Counter::query()->first();
  392.  
  393. return $counter ? $counter->toArray() : [];
  394. });
  395. }
  396.  
  397. /**
  398. * Выводит счетчик посещений
  399. *
  400. * @return HtmlString|null
  401. */
  402. function showCounter(): ?HtmlString
  403. {
  404. $metrika = new Metrika();
  405. $metrika->saveStatistic();
  406.  
  407. $counter = statsCounter();
  408.  
  409. if (setting('incount') > 0) {
  410. return new HtmlString(view('app/_counter', compact('counter')));
  411. }
  412.  
  413. return null;
  414. }
  415.  
  416. /**
  417. * Возвращает количество пользователей
  418. *
  419. * @return string Количество пользователей
  420. */
  421. function statsUsers(): string
  422. {
  423. return Cache::remember('statUsers', 1800, static function () {
  424. $stat = User::query()->count();
  425. $new = User::query()->where('created_at', '>', strtotime('-1 day', SITETIME))->count();
  426.  
  427. if ($new) {
  428. $stat .= '/+' . $new;
  429. }
  430.  
  431. return $stat;
  432. });
  433. }
  434.  
  435. /**
  436. * Возвращает количество администраторов
  437. *
  438. * @return int Количество администраторов
  439. */
  440. function statsAdmins(): int
  441. {
  442. return Cache::remember('statAdmins', 3600, static function () {
  443. return User::query()->whereIn('level', User::ADMIN_GROUPS)->count();
  444. });
  445. }
  446.  
  447. /**
  448. * Возвращает количество жалоб
  449. *
  450. * @return int Количество жалоб
  451. */
  452. function statsSpam(): int
  453. {
  454. return Spam::query()->count();
  455. }
  456.  
  457. /**
  458. * Возвращает количество забанненых пользователей
  459. *
  460. * @return int Количество забаненных
  461. */
  462. function statsBanned(): int
  463. {
  464. return User::query()
  465. ->where('level', User::BANNED)
  466. ->where('timeban', '>', SITETIME)
  467. ->count();
  468. }
  469.  
  470. /**
  471. * Возвращает количество записей в истории банов
  472. *
  473. * @return int Количество записей
  474. */
  475. function statsBanHist(): int
  476. {
  477. return Banhist::query()->count();
  478. }
  479.  
  480. /**
  481. * Возвращает количество ожидающих подтверждения регистрации
  482. *
  483. * @return int Количество ожидающих
  484. */
  485. function statsRegList(): int
  486. {
  487. return User::query()->where('level', User::PENDED)->count();
  488. }
  489.  
  490. /**
  491. * Возвращает количество забаненных по IP
  492. *
  493. * @return int Количество забаненных
  494. */
  495. function statsIpBanned(): int
  496. {
  497. return Ban::query()->count();
  498. }
  499.  
  500. /**
  501. * Возвращает количество фотографий в галерее
  502. *
  503. * @return string Количество фотографий
  504. */
  505. function statsPhotos(): string
  506. {
  507. return Cache::remember('statPhotos', 900, static function () {
  508. $stat = Photo::query()->count();
  509. $totalNew = Photo::query()->where('created_at', '>', strtotime('-1 day', SITETIME))->count();
  510.  
  511. return formatShortNum($stat) . ($totalNew ? '/+' . $totalNew : '');
  512. });
  513. }
  514.  
  515. /**
  516. * Возвращает количество новостей
  517. *
  518. * @return string Количество новостей
  519. */
  520. function statsNews(): string
  521. {
  522. return Cache::remember('statNews', 300, static function () {
  523. $total = News::query()->count();
  524.  
  525. $totalNew = News::query()
  526. ->where('created_at', '>', strtotime('-1 day', SITETIME))
  527. ->count();
  528.  
  529. return formatShortNum($total) . ($totalNew ? '/+' . $totalNew : '');
  530. });
  531. }
  532.  
  533. /**
  534. * Возвращает количество записей в черном списке
  535. *
  536. * @return string Количество записей
  537. */
  538. function statsBlacklist(): string
  539. {
  540. $blacklist = BlackList::query()
  541. ->selectRaw('type, count(*) as total')
  542. ->groupBy('type')
  543. ->pluck('total', 'type')
  544. ->all();
  545.  
  546. $list = $blacklist + ['login' => 0, 'email' => 0, 'domain' => 0];
  547.  
  548. return $list['login'] . '/' . $list['email'] . '/' . $list['domain'];
  549. }
  550.  
  551. /**
  552. * Возвращает количество записей в антимате
  553. *
  554. * @return int Количество записей
  555. */
  556. function statsAntimat(): int
  557. {
  558. return Antimat::query()->count();
  559. }
  560.  
  561. /**
  562. * Возвращает количество стикеров
  563. *
  564. * @return int Количество стикеров
  565. */
  566. function statsStickers(): int
  567. {
  568. return Sticker::query()->count();
  569. }
  570.  
  571. /**
  572. * Возвращает дату последнего сканирования сайта
  573. *
  574. * @return int|string Дата последнего сканирования
  575. */
  576. function statsChecker()
  577. {
  578. if (file_exists(storage_path('framework/cache/checker.php'))) {
  579. return dateFixed(filemtime(storage_path('framework/cache/checker.php')), 'd.m.Y');
  580. }
  581.  
  582. return 0;
  583. }
  584.  
  585. /**
  586. * Возвращает количество приглашений на регистрацию
  587. *
  588. * @return string Количество приглашений
  589. */
  590. function statsInvite(): string
  591. {
  592. $invited = Invite::query()->where('used', 0)->count();
  593. $usedInvited = Invite::query()->where('used', 1)->count();
  594.  
  595. return $invited . '/' . $usedInvited;
  596. }
  597.  
  598. /**
  599. * Возвращает следующею и предыдущую фотографию в галерее
  600. *
  601. * @param int $id Id фотографий
  602. *
  603. * @return array|null Массив данных
  604. */
  605. function photoNavigation(int $id): ?array
  606. {
  607. if (! $id) {
  608. return null;
  609. }
  610.  
  611. $next = Photo::query()
  612. ->where('id', '>', $id)
  613. ->orderBy('id')
  614. ->pluck('id')
  615. ->first();
  616.  
  617. $prev = Photo::query()
  618. ->where('id', '<', $id)
  619. ->orderByDesc('id')
  620. ->pluck('id')
  621. ->first();
  622.  
  623. return compact('next', 'prev');
  624. }
  625.  
  626. /**
  627. * Возвращает количество статей в блогах
  628. *
  629. * @return string Количество статей
  630. */
  631. function statsBlog(): string
  632. {
  633. return Cache::remember('statArticles', 900, static function () {
  634. $stat = Article::query()->count();
  635. $totalNew = Article::query()->where('created_at', '>', strtotime('-1 day', SITETIME))->count();
  636.  
  637. return formatShortNum($stat) . ($totalNew ? '/+' . $totalNew : '');
  638. });
  639. }
  640.  
  641. /**
  642. * Возвращает количество тем и сообщений в форуме
  643. *
  644. * @return string Количество тем и сообщений
  645. */
  646. function statsForum(): string
  647. {
  648. return Cache::remember('statForums', 600, static function () {
  649. $topics = Topic::query()->count();
  650. $posts = Post::query()->count();
  651.  
  652. $totalNew = Post::query()
  653. ->where('created_at', '>', strtotime('-1 day', SITETIME))
  654. ->count();
  655.  
  656. return formatShortNum($topics) . '/' . formatShortNum($posts) . ($totalNew ? '/+' . $totalNew : '');
  657. });
  658. }
  659.  
  660. /**
  661. * Возвращает количество сообщений в гостевой книге
  662. *
  663. * @return string Количество сообщений
  664. */
  665. function statsGuestbook(): string
  666. {
  667. return Cache::remember('statGuestbook', 600, static function () {
  668. $total = Guestbook::query()->count();
  669.  
  670. $totalNew = Guestbook::query()
  671. ->where('active', true)
  672. ->where('created_at', '>', strtotime('-1 day', SITETIME))
  673. ->count();
  674.  
  675. return formatShortNum($total) . ($totalNew ? '/+' . $totalNew : '');
  676. });
  677. }
  678.  
  679. /**
  680. * Возвращает количество сообщений в админ-чате
  681. *
  682. * @return string Количество сообщений
  683. */
  684. function statsChat(): string
  685. {
  686. return Cache::remember('statChat', 3600, static function () {
  687. $total = Chat::query()->count();
  688.  
  689. $totalNew = Chat::query()
  690. ->where('created_at', '>', strtotime('-1 day', SITETIME))
  691. ->count();
  692.  
  693. return formatShortNum($total) . ($totalNew ? '/+' . $totalNew : '');
  694. });
  695. }
  696.  
  697. /**
  698. * Возвращает время последнего сообщения в админ-чате
  699. *
  700. * @return int Время сообщения
  701. */
  702. function statsNewChat(): int
  703. {
  704. return Chat::query()->max('created_at') ?? 0;
  705. }
  706.  
  707. /**
  708. * Возвращает количество файлов в загруз-центре
  709. *
  710. * @return string Количество файлов
  711. */
  712. function statsLoad(): string
  713. {
  714. return Cache::remember('statLoads', 900, static function () {
  715. $totalLoads = Load::query()->sum('count_downs');
  716.  
  717. $totalNew = Down::query()->where('active', 1)
  718. ->where('created_at', '>', strtotime('-1 day', SITETIME))
  719. ->count();
  720.  
  721. return formatShortNum($totalLoads) . ($totalNew ? '/+' . $totalNew : '');
  722. });
  723. }
  724.  
  725. /**
  726. * Возвращает количество новых файлов
  727. *
  728. * @return int Количество файлов
  729. */
  730. function statsNewLoad(): int
  731. {
  732. return Down::query()->where('active', 0)->count();
  733. }
  734.  
  735. /**
  736. * Возвращает количество объявлений
  737. *
  738. * @return string Количество статей
  739. */
  740. function statsBoard(): string
  741. {
  742. return Cache::remember('statBoards', 900, static function () {
  743. $stat = formatShortNum(Item::query()->where('expires_at', '>', SITETIME)->count());
  744. $totalNew = Item::query()->where('updated_at', '>', strtotime('-1 day', SITETIME))->count();
  745.  
  746. return formatShortNum($stat) . ($totalNew ? '/+' . $totalNew : '');
  747. });
  748. }
  749.  
  750. /**
  751. * Обфусцирует email
  752. *
  753. * @param string $email email
  754. *
  755. * @return string Обфусцированный email
  756. */
  757. function cryptMail(string $email): string
  758. {
  759. $output = '';
  760. $symbols = mb_str_split($email);
  761.  
  762. foreach ($symbols as $symbol) {
  763. $output .= '&#' . ord($symbol) . ';';
  764. }
  765.  
  766. return $output;
  767. }
  768.  
  769. /**
  770. * Частично скрывает email
  771. *
  772. * @param string $email
  773. *
  774. * @return string
  775. */
  776. function hideMail(string $email): string
  777. {
  778. return preg_replace('/(?<=.).(?=.*@)/u', '*', $email);
  779. }
  780.  
  781. /**
  782. * Возвращает статистику текущих голосований из кэш-файла
  783. *
  784. * @return string Статистика текущий голосований
  785. */
  786. function statVotes(): string
  787. {
  788. return Cache::remember('statVotes', 900, static function () {
  789. $votes = Vote::query()
  790. ->selectRaw('count(*) AS cnt, coalesce(sum(count), 0) AS sum')
  791. ->where('closed', 0)
  792. ->first();
  793.  
  794. if (! $votes) {
  795. $votes->cnt = $votes->sum = 0;
  796. }
  797.  
  798. return $votes->cnt . '/' . $votes->sum;
  799. });
  800. }
  801.  
  802. /**
  803. * Возвращает дату последней новости из кэш-файла
  804. *
  805. * @return string Дата последней новости
  806. */
  807. function statsNewsDate()
  808. {
  809. $newsDate = Cache::remember('statNewsDate', 900, static function () {
  810. /** @var News $news */
  811. $news = News::query()->orderByDesc('created_at')->first();
  812.  
  813. return $news->created_at ?? 0;
  814. });
  815.  
  816. return $newsDate ? dateFixed($newsDate, 'd.m.Y') : 0;
  817. }
  818.  
  819. /**
  820. * Возвращает последние новости
  821. *
  822. * @return HtmlString Новость
  823. */
  824. function lastNews(): HtmlString
  825. {
  826. $news = collect();
  827.  
  828. if (setting('lastnews') > 0) {
  829. $news = Cache::remember('lastNews', 1800, static function () {
  830. return News::query()
  831. ->where('top', 1)
  832. ->orderByDesc('created_at')
  833. ->limit(setting('lastnews'))
  834. ->get();
  835. });
  836. }
  837.  
  838. return new HtmlString(view('widgets/_news', compact('news')));
  839. }
  840.  
  841. /**
  842. * Возвращает иконку расширения
  843. *
  844. * @param string $ext Расширение файла
  845. *
  846. * @return HtmlString Иконка
  847. */
  848. function icons(string $ext): HtmlString
  849. {
  850. switch ($ext) {
  851. case 'php':
  852. $ico = 'file-code';
  853. break;
  854. case 'ppt':
  855. $ico = 'file-powerpoint';
  856. break;
  857. case 'doc':
  858. case 'docx':
  859. $ico = 'file-word';
  860. break;
  861. case 'xls':
  862. case 'xlsx':
  863. $ico = 'file-excel';
  864. break;
  865. case 'txt':
  866. case 'css':
  867. case 'dat':
  868. case 'html':
  869. case 'htm':
  870. $ico = 'file-alt';
  871. break;
  872. case 'wav':
  873. case 'amr':
  874. case 'mp3':
  875. case 'mid':
  876. $ico = 'file-audio';
  877. break;
  878. case 'zip':
  879. case 'rar':
  880. case '7z':
  881. case 'gz':
  882. $ico = 'file-archive';
  883. break;
  884. case '3gp':
  885. case 'mp4':
  886. $ico = 'file-video';
  887. break;
  888. case 'jpg':
  889. case 'jpeg':
  890. case 'bmp':
  891. case 'wbmp':
  892. case 'gif':
  893. case 'png':
  894. $ico = 'file-image';
  895. break;
  896. case 'ttf':
  897. $ico = 'font';
  898. break;
  899. case 'pdf':
  900. $ico = 'file-pdf';
  901. break;
  902. default:
  903. $ico = 'file';
  904. }
  905. return new HtmlString('<i class="far fa-' . $ico . '"></i>');
  906. }
  907.  
  908. /**
  909. * Перемешивает элементы ассоциативного массива, сохраняя ключи
  910. *
  911. * @param array &$array Исходный массив, переданный по ссылке
  912. *
  913. * @return bool Флаг успешного выполнения операции
  914. */
  915. function shuffleAssoc(array &$array): bool
  916. {
  917. $keys = array_keys($array);
  918.  
  919. shuffle($keys);
  920. $new = [];
  921.  
  922. foreach ($keys as $key) {
  923. $new[$key] = $array[$key];
  924. }
  925.  
  926. $array = $new;
  927.  
  928. return true;
  929. }
  930.  
  931. /**
  932. * Закрывает bb-теги
  933. *
  934. * @param string $html
  935. *
  936. * @return string
  937. */
  938. function closeTags(string $html): string
  939. {
  940. preg_match_all('#\[([a-z]+)(?:=.*)?(?<![/])\]#iU', $html, $result);
  941. $openTags = $result[1];
  942.  
  943. preg_match_all('#\[/([a-z]+)\]#iU', $html, $result);
  944. $closedTags = $result[1];
  945.  
  946. if ($openTags === $closedTags) {
  947. return $html;
  948. }
  949.  
  950. $diff = array_diff_assoc($openTags, $closedTags);
  951. $tags = array_reverse($diff);
  952.  
  953. foreach ($tags as $value) {
  954. $html .= '[/' . $value . ']';
  955. }
  956.  
  957. return $html;
  958. }
  959.  
  960. /**
  961. * Возвращает обрезанный текст с закрытием тегов
  962. *
  963. * @param string|null $value
  964. * @param int $words
  965. * @param string $end
  966. *
  967. * @return HtmlString
  968. */
  969. function bbCodeTruncate(?string $value, int $words = 20, string $end = '...'): HtmlString
  970. {
  971. $value = Str::words($value, $words, $end);
  972. $bbText = bbCode(closeTags($value));
  973.  
  974. return new HtmlString(preg_replace('/\[(.*?)\]/', '', $bbText));
  975. }
  976.  
  977. /**
  978. * Возвращает обрезанную до заданного количества букв строке
  979. *
  980. * @param HtmlString|string $value Исходная строка
  981. * @param int $limit Максимальное количество символов в результате
  982. * @param string $end
  983. *
  984. * @return string Обрезанная строка
  985. */
  986. function truncateString($value, int $limit = 100, string $end = '...'): string
  987. {
  988. $value = strip_tags($value);
  989.  
  990. if (mb_strlen($value, 'utf-8') <= $limit) {
  991. return $value;
  992. }
  993.  
  994. $string = mb_substr($value, 0, $limit + 1);
  995. if ($lastSpace = mb_strrpos($string, ' ', 0, 'utf-8')) {
  996. $string = mb_substr($string, 0, $lastSpace, 'utf-8');
  997. } else {
  998. $string = mb_substr($string, 0, $limit, 'utf-8');
  999. }
  1000.  
  1001. return trim($string) . $end;
  1002. }
  1003.  
  1004. /**
  1005. * Возвращает обрезанную до заданного количества слов строке
  1006. *
  1007. * @param HtmlString|string $value Исходная строка
  1008. * @param int $words Максимальное количество слов в результате
  1009. * @param string $end
  1010. *
  1011. * @return string Обрезанная строка
  1012. */
  1013. function truncateWord($value, int $words = 20, string $end = '...'): string
  1014. {
  1015. $value = strip_tags($value);
  1016.  
  1017. return Str::words(trim($value), $words, $end);
  1018. }
  1019.  
  1020. /**
  1021. * Возвращает обрезанную строку с удалением перевода строки
  1022. *
  1023. * @param HtmlString|string $value
  1024. * @param int $words
  1025. * @param string $end
  1026. *
  1027. * @return string
  1028. */
  1029. function truncateDescription($value, int $words = 20, string $end = ''): string
  1030. {
  1031. $value = strip_tags(preg_replace('/\s+/', ' ', $value));
  1032.  
  1033. return Str::words(trim($value), $words, $end);
  1034. }
  1035.  
  1036. /**
  1037. * Get the number of words a string contains.
  1038. *
  1039. * @param string $string
  1040. * @return int
  1041. */
  1042. function wordCount($string): int
  1043. {
  1044. return count(preg_split('/[^\s*+]+/u', $string));
  1045. }
  1046.  
  1047. /**
  1048. * Возвращает код платной рекламы
  1049. *
  1050. * @param string $place
  1051. *
  1052. * @return HtmlString|null
  1053. */
  1054. function getAdvertPaid(string $place): ?HtmlString
  1055. {
  1056. $adverts = PaidAdvert::statAdverts();
  1057.  
  1058. if (isset($adverts[$place])) {
  1059. $links = [];
  1060. foreach ($adverts[$place] as $advert) {
  1061. $links[] = Arr::random($advert);
  1062. }
  1063.  
  1064. return new HtmlString(implode('<br>', $links));
  1065. }
  1066.  
  1067. return null;
  1068. }
  1069.  
  1070. /**
  1071. * Возвращает код админской рекламы
  1072. *
  1073. * @return HtmlString|null
  1074. */
  1075. function getAdvertAdmin(): ?HtmlString
  1076. {
  1077. $adverts = AdminAdvert::statAdverts();
  1078.  
  1079. if ($adverts) {
  1080. $result = Arr::random($adverts);
  1081.  
  1082. return new HtmlString(view('adverts/_admin_links', compact('result')));
  1083. }
  1084.  
  1085. return null;
  1086. }
  1087.  
  1088. /**
  1089. * Возвращает код пользовательской рекламы
  1090. *
  1091. * @return HtmlString|null
  1092. */
  1093. function getAdvertUser(): ?HtmlString
  1094. {
  1095. $adverts = Advert::statAdverts();
  1096.  
  1097. if ($adverts) {
  1098. $total = count($adverts);
  1099. $show = setting('rekusershow') > $total ? $total : setting('rekusershow');
  1100.  
  1101. $links = Arr::random($adverts, $show);
  1102. $result = implode('<br>', $links);
  1103.  
  1104. return new HtmlString(view('adverts/_links', compact('result')));
  1105. }
  1106.  
  1107. return null;
  1108. }
  1109.  
  1110. /**
  1111. * Выводит последние фотографии
  1112. *
  1113. * @param int $show Количество последних фотографий
  1114. *
  1115. * @return HtmlString Список фотографий
  1116. */
  1117. function recentPhotos(int $show = 5): HtmlString
  1118. {
  1119. $photos = Cache::remember('recentPhotos', 1800, static function () use ($show) {
  1120. return Photo::query()
  1121. ->orderByDesc('created_at')
  1122. ->limit($show)
  1123. ->with('files')
  1124. ->get();
  1125. });
  1126.  
  1127. return new HtmlString(view('widgets/_photos', compact('photos')));
  1128. }
  1129.  
  1130. /**
  1131. * Выводит последние темы форума
  1132. *
  1133. * @param int $show Количество последних тем форума
  1134. *
  1135. * @return HtmlString Список тем
  1136. */
  1137. function recentTopics(int $show = 5): HtmlString
  1138. {
  1139. $topics = Cache::remember('recentTopics', 300, static function () use ($show) {
  1140. return Topic::query()
  1141. ->orderByDesc('updated_at')
  1142. ->limit($show)
  1143. ->get();
  1144. });
  1145.  
  1146. return new HtmlString(view('widgets/_topics', compact('topics')));
  1147. }
  1148.  
  1149. /**
  1150. * Выводит последние файлы в загрузках
  1151. *
  1152. * @param int $show Количество последних файлов в загрузках
  1153. *
  1154. * @return HtmlString Список файлов
  1155. */
  1156. function recentDowns(int $show = 5): HtmlString
  1157. {
  1158. $downs = Cache::remember('recentDowns', 600, static function () use ($show) {
  1159. return Down::query()
  1160. ->where('active', 1)
  1161. ->orderByDesc('created_at')
  1162. ->limit($show)
  1163. ->with('category')
  1164. ->get();
  1165. });
  1166.  
  1167. return new HtmlString(view('widgets/_downs', compact('downs')));
  1168. }
  1169.  
  1170. /**
  1171. * Выводит последние статьи в блогах
  1172. *
  1173. * @param int $show Количество последних статей в блогах
  1174. *
  1175. * @return HtmlString Список статей
  1176. */
  1177. function recentArticles(int $show = 5): HtmlString
  1178. {
  1179. $articles = Cache::remember('recentArticles', 600, static function () use ($show) {
  1180. return Article::query()
  1181. ->orderByDesc('created_at')
  1182. ->limit($show)
  1183. ->get();
  1184. });
  1185.  
  1186. return new HtmlString(view('widgets/_articles', compact('articles')));
  1187. }
  1188.  
  1189. /**
  1190. * Выводит последние объявления
  1191. *
  1192. * @param int $show Количество последних объявлений
  1193. *
  1194. * @return HtmlString Список объявлений
  1195. */
  1196. function recentBoards(int $show = 5): HtmlString
  1197. {
  1198. $items = Cache::remember('recentBoards', 600, static function () use ($show) {
  1199. return Item::query()
  1200. ->where('expires_at', '>', SITETIME)
  1201. ->orderByDesc('created_at')
  1202. ->limit($show)
  1203. ->get();
  1204. });
  1205.  
  1206. return new HtmlString(view('widgets/_boards', compact('items')));
  1207. }
  1208.  
  1209. /**
  1210. * Возвращает количество предложений и проблем
  1211. *
  1212. * @return string Количество предложений и проблем
  1213. */
  1214. function statsOffers(): string
  1215. {
  1216. return Cache::remember('offers', 600, static function () {
  1217. $offers = Offer::query()->where('type', 'offer')->count();
  1218. $problems = Offer::query()->where('type', 'issue')->count();
  1219.  
  1220. return $offers . '/' . $problems;
  1221. });
  1222. }
  1223.  
  1224. /**
  1225. * Пересчитывает счетчики
  1226. *
  1227. * @param string $mode сервис счетчиков
  1228. *
  1229. * @return void
  1230. */
  1231. function restatement(string $mode)
  1232. {
  1233. switch ($mode) {
  1234. case 'forums':
  1235. DB::update('update topics set count_posts = (select count(*) from posts where topics.id = posts.topic_id)');
  1236. DB::update('update forums set count_topics = (select count(*) from topics where forums.id = topics.forum_id)');
  1237. DB::update('update forums set count_posts = (select coalesce(sum(count_posts), 0) from topics where forums.id = topics.forum_id)');
  1238. break;
  1239.  
  1240. case 'blogs':
  1241. DB::update('update blogs set count_articles = (select count(*) from articles where blogs.id = articles.category_id)');
  1242. DB::update('update articles set count_comments = (select count(*) from comments where relate_type = "' . Article::$morphName . '" and articles.id = comments.relate_id)');
  1243. break;
  1244.  
  1245. case 'loads':
  1246. DB::update('update loads set count_downs = (select count(*) from downs where loads.id = downs.category_id and active = ?)', [1]);
  1247. DB::update('update downs set count_comments = (select count(*) from comments where relate_type = "' . Down::$morphName . '" and downs.id = comments.relate_id)');
  1248. break;
  1249.  
  1250. case 'news':
  1251. DB::update('update news set count_comments = (select count(*) from comments where relate_type = "' . News::$morphName . '" and news.id = comments.relate_id)');
  1252. break;
  1253.  
  1254. case 'photos':
  1255. DB::update('update photos set count_comments = (select count(*) from comments where relate_type = "' . Photo::$morphName . '" and photos.id = comments.relate_id)');
  1256. break;
  1257.  
  1258. case 'offers':
  1259. DB::update('update offers set count_comments = (select count(*) from comments where relate_type = "' . Offer::$morphName . '" and offers.id = comments.relate_id)');
  1260. break;
  1261.  
  1262. case 'boards':
  1263. DB::update('update boards set count_items = (select count(*) from items where boards.id = items.board_id and items.expires_at > ' . SITETIME . ');');
  1264. break;
  1265.  
  1266. case 'votes':
  1267. DB::update('update votes set count = (select coalesce(sum(result), 0) from voteanswer where votes.id = voteanswer.vote_id)');
  1268. break;
  1269. }
  1270. }
  1271.  
  1272. /**
  1273. * Возвращает количество строк в файле
  1274. *
  1275. * @param string $file Путь к файлу
  1276. *
  1277. * @return int Количество строк
  1278. */
  1279. function counterString(string $file): int
  1280. {
  1281. $countLines = 0;
  1282. if (file_exists($file)) {
  1283. $countLines = count(file($file));
  1284. }
  1285.  
  1286. return $countLines;
  1287. }
  1288.  
  1289. /**
  1290. * Форматирует вывод числа
  1291. *
  1292. * @param int|float $num число
  1293. *
  1294. * @return HtmlString Форматированное число
  1295. */
  1296. function formatNum($num): HtmlString
  1297. {
  1298. if ($num > 0) {
  1299. $data = '<span style="color:#00aa00">+' . $num . '</span>';
  1300. } elseif ($num < 0) {
  1301. $data = '<span style="color:#ff0000">' . $num . '</span>';
  1302. } else {
  1303. $data = '<span>0</span>';
  1304. }
  1305.  
  1306. return new HtmlString($data);
  1307. }
  1308.  
  1309. /**
  1310. * Форматирует вывод числа
  1311. *
  1312. * @param int $num
  1313. *
  1314. * @return float|int|string
  1315. */
  1316. function formatShortNum(int $num): float|int|string
  1317. {
  1318. if (! is_numeric($num)) {
  1319. return '0b';
  1320. }
  1321.  
  1322. if ($num > 1000000000000) {
  1323. return round($num / 1000000000000, 1) . 'T';
  1324. }
  1325.  
  1326. if ($num > 1000000000) {
  1327. return round($num / 1000000000, 1) . 'B';
  1328. }
  1329.  
  1330. if ($num > 1000000) {
  1331. return round($num / 1000000, 1) . 'M';
  1332. }
  1333.  
  1334. if ($num > 1000) {
  1335. return round($num / 1000, 1) . 'K';
  1336. }
  1337.  
  1338. return $num;
  1339. }
  1340.  
  1341. /**
  1342. * Обрабатывает и уменьшает изображение
  1343. *
  1344. * @param string|null $path Путь к изображению
  1345. * @param array $params Параметры изображения
  1346. *
  1347. * @return array Обработанные параметры
  1348. */
  1349. function resizeProcess(?string $path, array $params = []): array
  1350. {
  1351. if (empty($params['alt'])) {
  1352. $params['alt'] = basename($path);
  1353. }
  1354.  
  1355. if (empty($params['class'])) {
  1356. $params['class'] = 'img-fluid';
  1357. }
  1358.  
  1359. if (! file_exists(public_path($path)) || ! is_file(public_path($path))) {
  1360. return [
  1361. 'path' => '/assets/img/images/photo.png',
  1362. 'source' => false,
  1363. 'params' => $params,
  1364. ];
  1365. }
  1366.  
  1367. [$width, $height] = getimagesize(public_path($path));
  1368.  
  1369. if ($width <= setting('previewsize') && $height <= setting('previewsize')) {
  1370. return [
  1371. 'path' => $path,
  1372. 'source' => $path,
  1373. 'params' => $params,
  1374. ];
  1375. }
  1376.  
  1377. $thumb = ltrim(str_replace('/', '_', $path), '_');
  1378.  
  1379. if (! file_exists(public_path('uploads/thumbnails/' . $thumb))) {
  1380. $img = Image::make(public_path($path));
  1381. $img->resize(setting('previewsize'), setting('previewsize'), static function (Constraint $constraint) {
  1382. $constraint->aspectRatio();
  1383. $constraint->upsize();
  1384. });
  1385.  
  1386. $img->save(public_path('uploads/thumbnails/' . $thumb));
  1387. }
  1388.  
  1389. return [
  1390. 'path' => '/uploads/thumbnails/' . $thumb,
  1391. 'source' => $path,
  1392. 'params' => $params,
  1393. ];
  1394. }
  1395.  
  1396. /**
  1397. * Возвращает уменьшенное изображение
  1398. *
  1399. * @param string|null $path Путь к изображению
  1400. * @param array $params Параметры изображения
  1401. *
  1402. * @return HtmlString Уменьшенное изображение
  1403. */
  1404. function resizeImage(?string $path, array $params = []): HtmlString
  1405. {
  1406. $image = resizeProcess($path, $params);
  1407.  
  1408. $strParams = [];
  1409. foreach ($image['params'] as $key => $param) {
  1410. $strParams[] = $key . '="' . check($param) . '"';
  1411. }
  1412.  
  1413. $strParams = implode(' ', $strParams);
  1414.  
  1415. return new HtmlString('<img src="' . $image['path'] . '" data-source="' . $image['source'] . '" ' . $strParams . '>');
  1416. }
  1417.  
  1418. /**
  1419. * Удаляет директорию рекурсивно
  1420. *
  1421. * @param string $dir путь к директории
  1422. *
  1423. * @return void
  1424. */
  1425. function deleteDir(string $dir)
  1426. {
  1427. if (file_exists($dir)) {
  1428. if ($files = glob($dir . '/*')) {
  1429. foreach ($files as $file) {
  1430. is_dir($file) ? deleteDir($file) : unlink($file);
  1431. }
  1432. }
  1433. rmdir($dir);
  1434. }
  1435. }
  1436.  
  1437. /**
  1438. * Удаляет файл
  1439. *
  1440. * @param string $path Путь к файлу
  1441. *
  1442. * @return bool
  1443. */
  1444. function deleteFile(string $path): bool
  1445. {
  1446. if (file_exists($path) && is_file($path)) {
  1447. unlink($path);
  1448. }
  1449.  
  1450. if (in_array(getExtension($path), ['jpg', 'jpeg', 'gif', 'png'], true)) {
  1451. $thumb = ltrim(str_replace([public_path(), '/'], ['', '_'], $path), '_');
  1452. $thumb = public_path('uploads/thumbnails/' . $thumb);
  1453.  
  1454. if (file_exists($thumb) && is_file($thumb)) {
  1455. unlink($thumb);
  1456. }
  1457. }
  1458.  
  1459. return true;
  1460. }
  1461.  
  1462. /**
  1463. * Отправляет уведомление об упоминании в приват
  1464. *
  1465. * @param string $text Текст сообщения
  1466. * @param string $url Путь к странице
  1467. * @param string $title Название страницу
  1468. *
  1469. * @return void
  1470. */
  1471. function sendNotify(string $text, string $url, string $title)
  1472. {
  1473. /*$parseText = preg_replace('|\[quote(.*?)\](.*?)\[/quote\]|s', '', $text);*/
  1474. preg_match_all('/(?<=^|\s|=)@([\w\-]+)/', $text, $matches);
  1475.  
  1476. if (! empty($matches[1])) {
  1477. $login = getUser('login') ?? setting('guestsuser');
  1478. $usersAnswer = array_unique(array_diff($matches[1], [$login]));
  1479.  
  1480. foreach ($usersAnswer as $user) {
  1481. $user = getUserByLogin($user);
  1482.  
  1483. if ($user && $user->notify) {
  1484. $notify = textNotice('notify', compact('login', 'url', 'title', 'text'));
  1485. $user->sendMessage(null, $notify);
  1486. }
  1487. }
  1488. }
  1489. }
  1490.  
  1491. /**
  1492. * Возвращает приватное сообщение
  1493. *
  1494. * @param string $type Тип сообщения
  1495. * @param array $replace Массив заменяемых параметров
  1496. *
  1497. * @return string Сформированный текст
  1498. */
  1499. function textNotice(string $type, array $replace = []): string
  1500. {
  1501. /** @var Notice $message */
  1502. $message = Notice::query()->where('type', $type)->first();
  1503.  
  1504. if (! $message) {
  1505. return __('main.text_missing');
  1506. }
  1507.  
  1508. foreach ($replace as $key => $val) {
  1509. $message->text = str_replace('%' . $key . '%', $val, $message->text);
  1510. }
  1511.  
  1512. return $message->text;
  1513. }
  1514.  
  1515. /**
  1516. * Возвращает блок статистики производительности
  1517. *
  1518. * @return HtmlString|null Статистика производительности
  1519. */
  1520. function performance(): ?HtmlString
  1521. {
  1522. if (isAdmin() && setting('performance')) {
  1523. $queries = getQueryLog();
  1524. $timeQueries = array_sum(array_column($queries, 'time'));
  1525.  
  1526. return new HtmlString(view('app/_performance', compact('queries', 'timeQueries')));
  1527. }
  1528.  
  1529. return null;
  1530. }
  1531.  
  1532. /**
  1533. * Очистка кеш-файлов
  1534. *
  1535. * @param array|string|null $keys
  1536. *
  1537. * @return bool Результат выполнения
  1538. */
  1539. function clearCache(array|string $keys = null): bool
  1540. {
  1541. if ($keys) {
  1542. if (!is_array($keys)) {
  1543. $keys = [$keys];
  1544. }
  1545.  
  1546. foreach ($keys as $key) {
  1547. Cache::forget($key);
  1548. }
  1549.  
  1550. return true;
  1551. }
  1552.  
  1553. Cache::flush();
  1554.  
  1555. return true;
  1556. }
  1557.  
  1558. /**
  1559. * Возвращает текущую страницу
  1560. *
  1561. * @param string|null $url
  1562. *
  1563. * @return string|null Текущая страница
  1564. */
  1565. function returnUrl(?string $url = null): ?string
  1566. {
  1567. $request = request();
  1568.  
  1569. if ($request->is('/', 'login', 'register', 'recovery', 'restore', 'ban', 'closed')) {
  1570. return null;
  1571. }
  1572.  
  1573. $query = $request->has('return') ? $request->input('return') : $request->path();
  1574.  
  1575. return '?return=' . urlencode($url ?? '/' . $query);
  1576. }
  1577.  
  1578. /**
  1579. * Saves error logs
  1580. *
  1581. * @param int $code
  1582. * @param string|null $message
  1583. *
  1584. * @return void
  1585. */
  1586. function saveErrorLog(int $code, ?string $message = null)
  1587. {
  1588. $errorCodes = [401, 403, 404, 405, 419, 429, 500, 503, 666];
  1589.  
  1590. if (setting('errorlog') && in_array($code, $errorCodes, true)) {
  1591. Error::query()->create([
  1592. 'code' => $code,
  1593. 'request' => utfSubstr(request()->getRequestUri(), 0, 250),
  1594. 'referer' => utfSubstr(request()->header('referer'), 0, 250),
  1595. 'user_id' => getUser('id'),
  1596. 'message' => utfSubstr($message, 0, 250),
  1597. 'ip' => getIp(),
  1598. 'brow' => getBrowser(),
  1599. 'created_at' => SITETIME,
  1600. ]);
  1601. }
  1602. }
  1603.  
  1604. /**
  1605. * Возвращает ошибку
  1606. *
  1607. * @param string|array $errors ошибки
  1608. *
  1609. * @return HtmlString Сформированный блок с ошибкой
  1610. */
  1611. function showError(string|array $errors): HtmlString
  1612. {
  1613. $errors = (array) $errors;
  1614.  
  1615. return new HtmlString(view('app/_error', compact('errors')));
  1616. }
  1617.  
  1618. /**
  1619. * @return HtmlString
  1620. */
  1621. function getCaptcha(): HtmlString
  1622. {
  1623. return new HtmlString(view('app/_captcha'));
  1624. }
  1625.  
  1626. /**
  1627. * Сохраняет flash уведомления
  1628. *
  1629. * @param string $status Статус уведомления
  1630. * @param mixed $message Массив или текст с уведомлениями
  1631. *
  1632. * @return void
  1633. *
  1634. * @deprecated since 10.1 - Use redirect->with('success', 'Message') or session()->flash()
  1635. */
  1636. function setFlash(string $status, $message)
  1637. {
  1638. session(['flash.' . $status => $message]);
  1639. }
  1640.  
  1641. /**
  1642. * Сохраняет POST данные введенных пользователем
  1643. *
  1644. * @param array $data Массив полей
  1645. *
  1646. * @deprecated since 10.1
  1647. */
  1648. function setInput(array $data)
  1649. {
  1650. session()->flash('input', json_encode($data));
  1651. }
  1652.  
  1653. /**
  1654. * Возвращает значение из POST данных
  1655. *
  1656. * @param string $key Имя поля
  1657. * @param mixed $default
  1658. *
  1659. * @return mixed Сохраненное значение
  1660. *
  1661. * @deprecated since 10.1 - Use old('field', 'default');
  1662. */
  1663. function getInput(string $key, $default = null): mixed
  1664. {
  1665. if (session()->missing('input')) {
  1666. return $default;
  1667. }
  1668.  
  1669. $input = json_decode(session('input', []), true);
  1670.  
  1671. return Arr::get($input, $key, $default);
  1672. }
  1673.  
  1674. /**
  1675. * Подсвечивает блок с полем для ввода сообщения
  1676. *
  1677. * @param string $field Имя поля
  1678. *
  1679. * @return string CSS класс ошибки
  1680. */
  1681. function hasError(string $field): string
  1682. {
  1683. // Новая валидация
  1684. if (session('errors')) {
  1685. /** @var ViewErrorBag $errors */
  1686. $errors = session('errors');
  1687.  
  1688. return $errors->has($field) ? ' is-invalid' : ' is-valid';
  1689. }
  1690.  
  1691. $isValid = session('flash.danger') ? ' is-valid' : '';
  1692.  
  1693. return session('flash.danger.' . $field) ? ' is-invalid' : $isValid;
  1694. }
  1695.  
  1696. /**
  1697. * Возвращает блок с текстом ошибки
  1698. *
  1699. * @param string $field Имя поля
  1700. *
  1701. * @return string|null Блоки ошибки
  1702. */
  1703. function textError(string $field): ?string
  1704. {
  1705. // Новая валидация
  1706. if (session('errors')) {
  1707. /** @var ViewErrorBag $errors */
  1708. $errors = session('errors');
  1709.  
  1710. return $errors->first($field);
  1711. }
  1712.  
  1713. return session('flash.danger.' . $field);
  1714. }
  1715.  
  1716. /**
  1717. * Отправляет уведомления на email
  1718. *
  1719. * @param string $view
  1720. * @param array $data
  1721. *
  1722. * @return bool Результат отправки
  1723. */
  1724. function sendMail(string $view, array $data): bool
  1725. {
  1726. try {
  1727. Mail::send($view, $data, static function (Message $message) use ($data) {
  1728. $message->subject($data['subject'])
  1729. ->to($data['to'])
  1730. ->from(config('mail.from.address'), config('mail.from.name'));
  1731.  
  1732. if (isset($data['from'])) {
  1733. [$fromEmail, $fromName] = $data['from'];
  1734. $message->replyTo($fromEmail, $fromName);
  1735. }
  1736.  
  1737. if (isset($data['unsubscribe'])) {
  1738. $headers = $message->getHeaders();
  1739. $headers->addTextHeader(
  1740. 'List-Unsubscribe',
  1741. '<' . config('app.url') . '/unsubscribe?key=' . $data['unsubscribe'] . '>'
  1742. );
  1743. }
  1744. });
  1745. } catch (Exception) {
  1746. return false;
  1747. }
  1748.  
  1749. return true;
  1750. }
  1751.  
  1752. /**
  1753. * Возвращает расширение файла
  1754. *
  1755. * @param string $filename Имя файла
  1756. *
  1757. * @return string расширение
  1758. */
  1759. function getExtension(string $filename): string
  1760. {
  1761. return pathinfo($filename, PATHINFO_EXTENSION);
  1762. }
  1763.  
  1764. /**
  1765. * Возвращает имя файла без расширения
  1766. *
  1767. * @param string $filename Имя файла
  1768. *
  1769. * @return string Имя без расширения
  1770. */
  1771. function getBodyName(string $filename): string
  1772. {
  1773. return pathinfo($filename, PATHINFO_FILENAME);
  1774. }
  1775.  
  1776. /**
  1777. * Склоняет числа
  1778. *
  1779. * @param int $num Число
  1780. * @param mixed $forms Склоняемые слова (один, два, много)
  1781. *
  1782. * @return string Форматированная строка
  1783. */
  1784. function plural(int $num, mixed $forms): string
  1785. {
  1786. if (! is_array($forms)) {
  1787. $forms = explode(',', $forms);
  1788. }
  1789.  
  1790. if (count($forms) === 1) {
  1791. return $num . ' ' . $forms[0];
  1792. }
  1793.  
  1794. if ($num % 100 > 10 && $num % 100 < 15) {
  1795. return $num . ' ' . $forms[2];
  1796. }
  1797.  
  1798. if ($num % 10 === 1) {
  1799. return $num . ' ' . $forms[0];
  1800. }
  1801.  
  1802. if ($num % 10 > 1 && $num % 10 < 5) {
  1803. return $num . ' ' . $forms[1];
  1804. }
  1805.  
  1806. return $num . ' ' . $forms[2];
  1807. }
  1808.  
  1809. /**
  1810. * Обрабатывает BB-код
  1811. *
  1812. * @param string|null $text Необработанный текст
  1813. * @param bool $parse Обрабатывать или вырезать код
  1814. *
  1815. * @return HtmlString Обработанный текст
  1816. */
  1817. function bbCode(?string $text, bool $parse = true): HtmlString
  1818. {
  1819. $bbCode = new BBCode();
  1820. $checkText = check($text);
  1821.  
  1822. if (! $parse) {
  1823. return new HtmlString($bbCode->clear($checkText));
  1824. }
  1825.  
  1826. $parseText = $bbCode->parse($checkText);
  1827. $parseText = $bbCode->parseStickers($parseText);
  1828.  
  1829. return new HtmlString($parseText);
  1830. }
  1831.  
  1832. /**
  1833. * Определяет IP пользователя
  1834. *
  1835. * @return string IP пользователя
  1836. */
  1837. function getIp(): string
  1838. {
  1839. return (new CloudFlare(request()))->ip();
  1840. }
  1841.  
  1842. /**
  1843. * Определяет браузер
  1844. *
  1845. * @param string|null $userAgent
  1846. *
  1847. * @return string Браузер и версия браузера
  1848. */
  1849. function getBrowser(string $userAgent = null): string
  1850. {
  1851. $browser = new Browser();
  1852. if ($userAgent) {
  1853. $browser->setUserAgent($userAgent);
  1854. }
  1855.  
  1856. $brow = $browser->getBrowser();
  1857. $version = implode('.', array_slice(explode('.', $browser->getVersion()), 0, 2));
  1858.  
  1859. $browser = $version === Browser::VERSION_UNKNOWN ? $brow : $brow . ' ' . $version;
  1860.  
  1861. return mb_substr($browser, 0, 25, 'utf-8');
  1862. }
  1863.  
  1864. /**
  1865. * Является ли пользователь администратором
  1866. *
  1867. * @param string|null $level Уровень доступа
  1868. *
  1869. * @return bool Является ли пользователь администратором
  1870. */
  1871. function isAdmin(?string $level = null): bool
  1872. {
  1873. return access($level ?? User::EDITOR);
  1874. }
  1875.  
  1876. /**
  1877. * Имеет ли пользователь доступ по уровню
  1878. *
  1879. * @param string $level Уровень доступа
  1880. *
  1881. * @return bool Разрешен ли доступ
  1882. */
  1883. function access(string $level): bool
  1884. {
  1885. $access = array_flip(User::ALL_GROUPS);
  1886.  
  1887. return getUser()
  1888. && isset($access[$level], $access[getUser('level')])
  1889. && $access[getUser('level')] <= $access[$level];
  1890. }
  1891.  
  1892. /**
  1893. * Возвращает текущего пользователя
  1894. *
  1895. * @return User|null
  1896. */
  1897. function getUserFromSession(): ?User
  1898. {
  1899. if (session()->has(['id', 'password'])) {
  1900. $user = getUserById(session('id'));
  1901.  
  1902. if ($user && session('password') === $user->password) {
  1903. return $user;
  1904. }
  1905. }
  1906.  
  1907. return null;
  1908. }
  1909.  
  1910. /**
  1911. * Возвращает объект пользователя по логину
  1912. *
  1913. * @param string|null $login Логин пользователя
  1914. *
  1915. * @return Builder|User|null
  1916. */
  1917. function getUserByLogin(?string $login): Builder|User|null
  1918. {
  1919. return User::query()->where('login', $login)->first();
  1920. }
  1921.  
  1922. /**
  1923. * Возвращает объект пользователя по id
  1924. *
  1925. * @param int|null $id ID пользователя
  1926. *
  1927. * @return Builder|User|null
  1928. */
  1929. function getUserById(?int $id): Builder|User|null
  1930. {
  1931. return User::query()->find($id);
  1932. }
  1933.  
  1934. /**
  1935. * Возвращает объект пользователя по токену
  1936. *
  1937. * @param string $token Логин пользователя
  1938. *
  1939. * @return Builder|User|null
  1940. */
  1941. function getUserByToken(string $token): Builder|User|null
  1942. {
  1943. return User::query()->where('apikey', $token)->first();
  1944. }
  1945.  
  1946. /**
  1947. * Возвращает объект пользователя по логину или email
  1948. *
  1949. * @param string|null $login Логин или email пользователя
  1950. *
  1951. * @return Builder|User|null
  1952. */
  1953. function getUserByLoginOrEmail(?string $login): Builder|User|null
  1954. {
  1955. $field = strpos($login, '@') ? 'email' : 'login';
  1956.  
  1957. return User::query()->where($field, $login)->first();
  1958. }
  1959.  
  1960. /**
  1961. * Возвращает данные пользователя по ключу
  1962. *
  1963. * @param string|null $key Ключ массива
  1964. *
  1965. * @return User|null|mixed
  1966. */
  1967. function getUser(?string $key = null): mixed
  1968. {
  1969. static $user;
  1970.  
  1971. if (! $user) {
  1972. $user = getUserFromSession();
  1973. }
  1974.  
  1975. return $key ? ($user->$key ?? null) : $user;
  1976. }
  1977.  
  1978. /**
  1979. * Разбивает данные по страницам
  1980. *
  1981. * @param array|Collection $items
  1982. * @param int $perPage
  1983. * @param array $appends
  1984. *
  1985. * @return LengthAwarePaginator
  1986. */
  1987. function paginate(array|Collection $items, int $perPage, array $appends = []): LengthAwarePaginator
  1988. {
  1989. $data = $items instanceof Collection ? $items : Collection::make($items);
  1990.  
  1991. $currentPage = LengthAwarePaginator::resolveCurrentPage();
  1992.  
  1993. $collection = new LengthAwarePaginator(
  1994. $data->forPage($currentPage, $perPage),
  1995. $data->count(),
  1996. $perPage,
  1997. $currentPage
  1998. );
  1999.  
  2000. $collection->setPath(request()->url());
  2001. $collection->appends($appends);
  2002.  
  2003. return $collection;
  2004. }
  2005.  
  2006. /**
  2007. * Разбивает данные по страницам
  2008. *
  2009. * @param array|Collection $items
  2010. * @param int $perPage
  2011. * @param array $appends
  2012. *
  2013. * @return Paginator
  2014. */
  2015. function simplePaginate(array|Collection $items, int $perPage, array $appends = []): Paginator
  2016. {
  2017. $data = $items instanceof Collection ? $items : Collection::make($items);
  2018.  
  2019. $currentPage = Paginator::resolveCurrentPage();
  2020.  
  2021. $collection = new Paginator(
  2022. $data->slice(max(0, ($currentPage - 1) * $perPage)),
  2023. $perPage
  2024. );
  2025.  
  2026. $collection->setPath(request()->url());
  2027. $collection->appends($appends);
  2028.  
  2029. return $collection;
  2030. }
  2031.  
  2032. /**
  2033. * Возвращает сформированный код base64 картинки
  2034. *
  2035. * @param string $path Путь к картинке
  2036. * @param array $params Параметры
  2037. *
  2038. * @return HtmlString Сформированный код
  2039. */
  2040. function imageBase64(string $path, array $params = []): HtmlString
  2041. {
  2042. $type = getExtension($path);
  2043. $data = file_get_contents($path);
  2044.  
  2045. if (! isset($params['class'])) {
  2046. $params['class'] = 'img-fluid';
  2047. }
  2048.  
  2049. if (empty($params['alt'])) {
  2050. $params['alt'] = basename($path);
  2051. }
  2052.  
  2053. $strParams = [];
  2054. foreach ($params as $key => $param) {
  2055. $strParams[] = $key . '="' . $param . '"';
  2056. }
  2057.  
  2058. $strParams = implode(' ', $strParams);
  2059.  
  2060. return new HtmlString('<img src="data:image/' . $type . ';base64,' . base64_encode($data) . '"' . $strParams . '>');
  2061. }
  2062.  
  2063. /**
  2064. * Выводит прогресс-бар
  2065. *
  2066. * @param int $percent
  2067. * @param float|int|string|null $title
  2068. *
  2069. * @return HtmlString
  2070. */
  2071. function progressBar(int $percent, float|int|string $title = null): HtmlString
  2072. {
  2073. if (! $title) {
  2074. $title = $percent . '%';
  2075. }
  2076.  
  2077. return new HtmlString(view('app/_progressbar', compact('percent', 'title')));
  2078. }
  2079.  
  2080. /**
  2081. * Возвращает форматированный список запросов
  2082. *
  2083. * @return array
  2084. */
  2085. function getQueryLog(): array
  2086. {
  2087. $queries = DB::getQueryLog();
  2088.  
  2089. $formattedQueries = [];
  2090.  
  2091. foreach ($queries as $query) {
  2092. foreach ($query['bindings'] as $key => $binding) {
  2093. if (is_string($binding)) {
  2094. $query['bindings'][$key] = ctype_print($binding) ? "'$binding'" : '[binary]';
  2095. }
  2096.  
  2097. $query['bindings'][$key] = $binding ?? 'null';
  2098. }
  2099.  
  2100. $sql = str_replace(['%', '?'], ['%%', '%s'], $query['query']);
  2101. $sql = vsprintf($sql, $query['bindings']);
  2102.  
  2103. $formattedQueries[] = ['query' => $sql, 'time' => $query['time']];
  2104. }
  2105.  
  2106. return $formattedQueries;
  2107. }
  2108.  
  2109. /**
  2110. * Выводит список забаненных ip
  2111. *
  2112. * @param bool $clear Нужно ли сбросить кеш
  2113. *
  2114. * @return array Массив IP
  2115. */
  2116. function ipBan(bool $clear = false): array
  2117. {
  2118. if ($clear) {
  2119. clearCache('ipBan');
  2120. }
  2121.  
  2122. return Cache::rememberForever('ipBan', static function () {
  2123. return Ban::query()->get()->pluck('id', 'ip')->all();
  2124. });
  2125. }
  2126.  
  2127. /**
  2128. * Возвращает настройки сайта по ключу
  2129. *
  2130. * @param string|null $key Ключ массива
  2131. * @param mixed|null $default Значение по умолчанию
  2132. *
  2133. * @return mixed Данные
  2134. */
  2135. function setting(?string $key = null, mixed $default = null)
  2136. {
  2137. static $settings;
  2138.  
  2139. if (! $settings) {
  2140. $settings = Setting::getSettings();
  2141. }
  2142.  
  2143. return $key ? ($settings[$key] ?? $default) : $settings;
  2144. }
  2145.  
  2146. /**
  2147. * Возвращает имя сайта из ссылки
  2148. *
  2149. * @param string $url Ссылка на сайт
  2150. *
  2151. * @return string Имя сайта
  2152. */
  2153. function siteDomain(string $url): string
  2154. {
  2155. return parse_url(strtolower($url), PHP_URL_HOST);
  2156. }
  2157.  
  2158. /**
  2159. * Получает версию
  2160. *
  2161. * @param string $version
  2162. *
  2163. * @return string
  2164. */
  2165. function parseVersion(string $version): string
  2166. {
  2167. $ver = explode('.', strtok($version, '-'));
  2168.  
  2169. return $ver[0] . '.' . $ver[1] . '.' . ($ver[2] ?? 0);
  2170. }
  2171.  
  2172. /**
  2173. * Проверяет captcha
  2174. *
  2175. * @return bool
  2176. */
  2177. function captchaVerify(): bool
  2178. {
  2179. $request = request();
  2180.  
  2181. if (setting('captcha_type') === 'recaptcha_v2') {
  2182. $recaptcha = new ReCaptcha(setting('recaptcha_private'));
  2183.  
  2184. $response = $recaptcha->setExpectedHostname($_SERVER['SERVER_NAME'])
  2185. ->verify($request->input('g-recaptcha-response'), getIp());
  2186.  
  2187. return $response->isSuccess();
  2188. }
  2189.  
  2190. if (setting('captcha_type') === 'recaptcha_v3') {
  2191. $recaptcha = new ReCaptcha(setting('recaptcha_private'));
  2192.  
  2193. $response = $recaptcha->setExpectedHostname($_SERVER['SERVER_NAME'])
  2194. ->setScoreThreshold(0.5)
  2195. ->verify($request->input('protect'), getIp());
  2196.  
  2197. return $response->isSuccess();
  2198. }
  2199.  
  2200. if (in_array(setting('captcha_type'), ['graphical', 'animated'], true)) {
  2201. return strtolower($request->input('protect')) === $request->session()->get('protect');
  2202. }
  2203.  
  2204. return false;
  2205. }
  2206.  
  2207. /**
  2208. * Возвращает уникальное имя
  2209. *
  2210. * @param string|null $extension
  2211. *
  2212. * @return string
  2213. */
  2214. function uniqueName(string $extension = null): string
  2215. {
  2216. if ($extension) {
  2217. $extension = '.' . $extension;
  2218. }
  2219.  
  2220. return str_replace('.', '', uniqid('', true)) . $extension;
  2221. }
  2222.  
  2223. /**
  2224. * Возвращает курсы валют
  2225. *
  2226. * @return HtmlString|null
  2227. */
  2228. function getCourses(): ?HtmlString
  2229. {
  2230. $courses = Cache::remember('courses', 3600, static function () {
  2231. try {
  2232. $client = new Client(['timeout' => 3.0]);
  2233. $response = $client->get('//www.cbr-xml-daily.ru/daily_json.js');
  2234.  
  2235. $content = json_decode($response->getBody()->getContents(), true);
  2236. } catch (Exception) {
  2237. $content = null;
  2238. }
  2239.  
  2240. return $content;
  2241. });
  2242.  
  2243. return new HtmlString(view('app/_courses', compact('courses')));
  2244. }