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

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