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

Размер файла: 21.17Kb
  1. <?php
  2.  
  3. declare(strict_types=1);
  4.  
  5. namespace App\Models;
  6.  
  7. use App\Traits\UploadTrait;
  8. use Exception;
  9. use GuzzleHttp\Client;
  10. use GuzzleHttp\Exception\GuzzleException;
  11. use Illuminate\Database\Eloquent\Builder;
  12. use Illuminate\Database\Eloquent\Model;
  13. use Illuminate\Database\Eloquent\Relations\BelongsTo;
  14. use Illuminate\Database\Eloquent\Relations\HasOne;
  15. use Illuminate\Database\Query\JoinClause;
  16. use Illuminate\Support\Facades\Cache;
  17. use Illuminate\Support\HtmlString;
  18.  
  19. /**
  20. * Class User
  21. *
  22. * @property int id
  23. * @property string login
  24. * @property string password
  25. * @property string email
  26. * @property string level
  27. * @property string name
  28. * @property string country
  29. * @property string city
  30. * @property string language
  31. * @property string info
  32. * @property string site
  33. * @property string phone
  34. * @property string gender
  35. * @property string birthday
  36. * @property int visits
  37. * @property int newprivat
  38. * @property int newwall
  39. * @property int allforum
  40. * @property int allguest
  41. * @property int allcomments
  42. * @property string themes
  43. * @property string timezone
  44. * @property int point
  45. * @property int money
  46. * @property string status
  47. * @property string color
  48. * @property string avatar
  49. * @property string picture
  50. * @property int rating
  51. * @property int posrating
  52. * @property int negrating
  53. * @property string keypasswd
  54. * @property int timepasswd
  55. * @property int sendprivatmail
  56. * @property int timebonus
  57. * @property string confirmregkey
  58. * @property int newchat
  59. * @property int notify
  60. * @property string apikey
  61. * @property string subscribe
  62. * @property int timeban
  63. * @property int updated_at
  64. * @property int created_at
  65. */
  66. class User extends BaseModel
  67. {
  68. use UploadTrait;
  69.  
  70. public const BOSS = 'boss'; // Владелец
  71. public const ADMIN = 'admin'; // Админ
  72. public const MODER = 'moder'; // Модератор
  73. public const EDITOR = 'editor'; // Редактор
  74. public const USER = 'user'; // Пользователь
  75. public const PENDED = 'pended'; // Ожидающий
  76. public const BANNED = 'banned'; // Забаненный
  77.  
  78. /**
  79. * Администраторы
  80. */
  81. public const ADMIN_GROUPS = [
  82. self::BOSS,
  83. self::ADMIN,
  84. self::MODER,
  85. self::EDITOR,
  86. ];
  87.  
  88. /**
  89. * Участники
  90. */
  91. public const USER_GROUPS = [
  92. self::BOSS,
  93. self::ADMIN,
  94. self::MODER,
  95. self::EDITOR,
  96. self::USER,
  97. ];
  98.  
  99. /**
  100. * Все пользователи
  101. */
  102. public const ALL_GROUPS = [
  103. self::BOSS,
  104. self::ADMIN,
  105. self::MODER,
  106. self::EDITOR,
  107. self::USER,
  108. self::PENDED,
  109. self::BANNED,
  110. ];
  111.  
  112. /**
  113. * Genders
  114. */
  115. public const MALE = 'male';
  116. public const FEMALE = 'female';
  117.  
  118.  
  119. /**
  120. * Indicates if the model should be timestamped.
  121. *
  122. * @var bool
  123. */
  124. public $timestamps = false;
  125.  
  126. /**
  127. * The attributes that aren't mass assignable.
  128. *
  129. * @var array
  130. */
  131. protected $guarded = [];
  132.  
  133. /**
  134. * Директория загрузки файлов
  135. *
  136. * @var string
  137. */
  138. public $uploadPath = UPLOADS . '/pictures';
  139.  
  140. /**
  141. * Директория загрузки аватаров
  142. *
  143. * @var string
  144. */
  145. public $uploadAvatarPath = UPLOADS . '/avatars';
  146.  
  147. /**
  148. * Связь с таблицей online
  149. *
  150. * @return BelongsTo
  151. */
  152. public function online(): BelongsTo
  153. {
  154. return $this->belongsTo(Online::class, 'id', 'user_id')->withDefault();
  155. }
  156.  
  157. /**
  158. * Возвращает последний бан
  159. *
  160. * @return hasOne
  161. */
  162. public function lastBan(): hasOne
  163. {
  164. return $this->hasOne(Banhist::class, 'user_id', 'id')
  165. ->whereIn('type', ['ban', 'change'])
  166. ->orderByDesc('created_at')
  167. ->withDefault();
  168. }
  169.  
  170. /**
  171. * Возвращает заметку пользователя
  172. *
  173. * @return hasOne
  174. */
  175. public function note(): HasOne
  176. {
  177. return $this->hasOne(Note::class)->withDefault();
  178. }
  179.  
  180. /**
  181. * Возвращает имя или логин пользователя
  182. *
  183. * @return string
  184. */
  185. public function getName(): string
  186. {
  187. if ($this->id) {
  188. return $this->name ?: $this->login;
  189. }
  190.  
  191. return setting('deleted_user');
  192. }
  193.  
  194. /**
  195. * Возвращает ссылку на профиль пользователя
  196. *
  197. * @return HtmlString путь к профилю
  198. */
  199. public function getProfile(): HtmlString
  200. {
  201. if ($this->id) {
  202. $admin = null;
  203. $name = check($this->getName());
  204.  
  205. if ($this->color) {
  206. $name = '<span style="color:' . $this->color . '">' . $name . '</span>';
  207. }
  208.  
  209. if (in_array($this->level, self::ADMIN_GROUPS, true)) {
  210. $admin = ' <i class="fas fa-xs fa-star text-info" title="' . $this->getLevel() . '"></i>';
  211. }
  212.  
  213. $html = '<a class="section-author fw-bold" href="/users/' . $this->login . '" data-login="@' . $this->login . '">' . $name . '</a>';
  214.  
  215. return new HtmlString($html . $admin);
  216. }
  217.  
  218. $html = '<span class="section-author fw-bold" data-login="' . setting('deleted_user') . '">' . setting('deleted_user') . '</span>';
  219.  
  220. return new HtmlString($html);
  221. }
  222.  
  223. /**
  224. * Возвращает пол пользователя
  225. *
  226. * @return HtmlString пол пользователя
  227. */
  228. public function getGender(): HtmlString
  229. {
  230. if ($this->gender === 'female') {
  231. return new HtmlString('<i class="fa fa-female fa-lg"></i>');
  232. }
  233.  
  234. return new HtmlString('<i class="fa fa-male fa-lg"></i>');
  235. }
  236.  
  237. /**
  238. * Авторизует пользователя
  239. *
  240. * @param string $login Логин
  241. * @param string $password Пароль пользователя
  242. * @param bool $remember Запомнить пароль
  243. * @return Builder|User|bool
  244. */
  245. public static function auth(string $login, string $password, bool $remember = true)
  246. {
  247. if (! empty($login) && ! empty($password)) {
  248. $user = getUserByLoginOrEmail($login);
  249.  
  250. if ($user && password_verify($password, $user->password)) {
  251. (new self())->rememberUser($user, $remember);
  252.  
  253. // Сохранение привязки к соц. сетям
  254. if (! empty($_SESSION['social'])) {
  255. Social::query()->create([
  256. 'user_id' => $user->id,
  257. 'network' => $_SESSION['social']->network,
  258. 'uid' => $_SESSION['social']->uid,
  259. 'created_at' => SITETIME,
  260. ]);
  261. }
  262.  
  263. $user->saveVisit(Login::AUTH);
  264.  
  265. return $user;
  266. }
  267. }
  268.  
  269. return false;
  270. }
  271.  
  272. /**
  273. * Авторизует через социальные сети
  274. *
  275. * @param string $token идентификатор Ulogin
  276. *
  277. * @return void
  278. * @throws GuzzleException
  279. */
  280. public static function socialAuth(string $token): void
  281. {
  282. $client = new Client(['timeout' => 30.0]);
  283.  
  284. $response = $client->get('//ulogin.ru/token.php', [
  285. 'query' => [
  286. 'token' => $token,
  287. 'host' => $_SERVER['HTTP_HOST'],
  288. ]
  289. ]);
  290.  
  291. if ($response->getStatusCode() === 200) {
  292. $network = json_decode($response->getBody()->getContents());
  293.  
  294. $_SESSION['social'] = $network;
  295.  
  296. /** @var Social $social */
  297. $social = Social::query()
  298. ->where('network', $network->network)
  299. ->where('uid', $network->uid)
  300. ->first();
  301.  
  302. if ($social && $user = getUserById($social->user_id)) {
  303. (new self())->rememberUser($user, true);
  304.  
  305. $user->saveVisit(Login::SOCIAL);
  306.  
  307. setFlash('success', __('users.welcome', ['login' => $user->getName()]));
  308. redirect('/');
  309. }
  310. }
  311. }
  312.  
  313. /**
  314. * Возвращает название уровня по ключу
  315. *
  316. * @param string $level
  317. * @return string
  318. */
  319. public static function getLevelByKey(string $level): string
  320. {
  321. switch ($level) {
  322. case self::BOSS:
  323. $status = __('main.boss');
  324. break;
  325. case self::ADMIN:
  326. $status = __('main.admin');
  327. break;
  328. case self::MODER:
  329. $status = __('main.moder');
  330. break;
  331. case self::EDITOR:
  332. $status = __('main.editor');
  333. break;
  334. case self::USER:
  335. $status = __('main.user');
  336. break;
  337. case self::PENDED:
  338. $status = __('main.pended');
  339. break;
  340. case self::BANNED:
  341. $status = __('main.banned');
  342. break;
  343. default:
  344. $status = setting('statusdef');
  345. }
  346.  
  347. return $status;
  348. }
  349.  
  350. /**
  351. * Возвращает уровень пользователя
  352. *
  353. * @return string Уровень пользователя
  354. */
  355. public function getLevel(): string
  356. {
  357. return self::getLevelByKey($this->level);
  358. }
  359.  
  360. /**
  361. * Is user online
  362. *
  363. * @return bool
  364. */
  365. public function isOnline(): bool
  366. {
  367. static $visits;
  368.  
  369. if (! $visits) {
  370. $visits = Cache::remember('visit', 10, static function () {
  371. return Online::query()
  372. ->whereNotNull('user_id')
  373. ->pluck('user_id', 'user_id')
  374. ->all();
  375. });
  376. }
  377.  
  378. return isset($visits[$this->id]);
  379. }
  380.  
  381. /**
  382. * User online status
  383. *
  384. * @return HtmlString онлайн-статус
  385. */
  386. public function getOnline(): HtmlString
  387. {
  388. $online = '';
  389.  
  390. if ($this->isOnline()) {
  391. $online = '<div class="user-status bg-success" title="' . __('main.online') . '"></div>';
  392. }
  393.  
  394. return new HtmlString($online);
  395. }
  396.  
  397. /**
  398. * Get last visit
  399. *
  400. * @return string
  401. */
  402. public function getVisit(): string
  403. {
  404. if ($this->isOnline()) {
  405. $visit = __('main.online');
  406. } else {
  407. $visit = dateFixed($this->updated_at);
  408. }
  409.  
  410. return $visit;
  411. }
  412.  
  413. /**
  414. * Возвращает статус пользователя
  415. *
  416. * @return HtmlString|string статус пользователя
  417. */
  418. public function getStatus()
  419. {
  420. static $status;
  421.  
  422. if (! $this->id) {
  423. return setting('statusdef');
  424. }
  425.  
  426. if (! $status) {
  427. $status = $this->getStatuses(6 * 3600);
  428. }
  429.  
  430. if (isset($status[$this->id])) {
  431. return new HtmlString($status[$this->id]);
  432. }
  433.  
  434. return setting('statusdef');
  435. }
  436.  
  437. /**
  438. * Возвращает аватар пользователя
  439. *
  440. * @return HtmlString аватар пользователя
  441. */
  442. public function getAvatar(): HtmlString
  443. {
  444. if (! $this->id) {
  445. return new HtmlString($this->getAvatarGuest());
  446. }
  447.  
  448. if ($this->avatar && file_exists(HOME . '/' . $this->avatar)) {
  449. $avatar = $this->getAvatarImage();
  450. } else {
  451. $avatar = $this->getAvatarDefault();
  452. }
  453.  
  454. return new HtmlString('<a href="/users/' . $this->login . '">' . $avatar . '</a> ');
  455. }
  456.  
  457. /**
  458. * Возвращает изображение аватара
  459. *
  460. * @return HtmlString
  461. */
  462. public function getAvatarImage(): HtmlString
  463. {
  464. if (! $this->id) {
  465. return $this->getAvatarGuest();
  466. }
  467.  
  468. if ($this->avatar && file_exists(HOME . '/' . $this->avatar)) {
  469. return new HtmlString('<img class="avatar-default rounded-circle" src="' . $this->avatar . '" alt="">');
  470. }
  471.  
  472. return $this->getAvatarDefault();
  473. }
  474.  
  475. /**
  476. * Get guest avatar
  477. *
  478. * @return HtmlString
  479. */
  480. public function getAvatarGuest(): HtmlString
  481. {
  482. return new HtmlString('<img class="avatar-default rounded-circle" src="/assets/img/images/avatar_guest.png" alt=""> ');
  483. }
  484.  
  485. /**
  486. * Возвращает аватар для пользователя по умолчанию
  487. *
  488. * @return HtmlString код аватара
  489. */
  490. private function getAvatarDefault(): HtmlString
  491. {
  492. $name = $this->getName();
  493. $color = '#' . substr(dechex(crc32($this->login)), 0, 6);
  494. $letter = mb_strtoupper(utfSubstr($name, 0, 1), 'utf-8');
  495.  
  496. return new HtmlString('<span class="avatar-default rounded-circle" style="background:' . $color . '">' . $letter . '</span>');
  497. }
  498.  
  499. /**
  500. * Кеширует статусы пользователей
  501. *
  502. * @param int $seconds время кеширования
  503. *
  504. * @return array
  505. */
  506. public function getStatuses(int $seconds): array
  507. {
  508. return Cache::remember('status', $seconds, static function () {
  509. $users = self::query()
  510. ->select('users.id', 'users.status', 'status.name', 'status.color')
  511. ->leftJoin('status', static function (JoinClause $join) {
  512. $join->whereRaw('users.point between status.topoint and status.point');
  513. })
  514. ->where('users.point', '>', 0)
  515. ->toBase()
  516. ->get();
  517.  
  518. $statuses = [];
  519. foreach ($users as $user) {
  520. if ($user->status) {
  521. $statuses[$user->id] = '<span style="color:#ff0000">' . check($user->status) . '</span>';
  522. continue;
  523. }
  524.  
  525. if ($user->color) {
  526. $statuses[$user->id] = '<span style="color:' . $user->color . '">' . check($user->name) . '</span>';
  527. continue;
  528. }
  529.  
  530. $statuses[$user->id] = check($user->name);
  531. }
  532.  
  533. return $statuses;
  534. });
  535. }
  536.  
  537. /**
  538. * Возвращает находится ли пользователь в контакатх
  539. *
  540. * @param User $user объект пользователя
  541. * @return bool находится ли в контактах
  542. */
  543. public function isContact(User $user): bool
  544. {
  545. $isContact = Contact::query()
  546. ->where('user_id', $this->id)
  547. ->where('contact_id', $user->id)
  548. ->first();
  549.  
  550. if ($isContact) {
  551. return true;
  552. }
  553.  
  554. return false;
  555. }
  556.  
  557. /**
  558. * Возвращает находится ли пользователь в игноре
  559. *
  560. * @param User $user объект пользователя
  561. * @return bool находится ли в игноре
  562. */
  563. public function isIgnore(User $user): bool
  564. {
  565. $isIgnore = Ignore::query()
  566. ->where('user_id', $this->id)
  567. ->where('ignore_id', $user->id)
  568. ->first();
  569.  
  570. if ($isIgnore) {
  571. return true;
  572. }
  573.  
  574. return false;
  575. }
  576.  
  577. /**
  578. * Отправляет приватное сообщение
  579. *
  580. * @param User|null $author Отправитель
  581. * @param string $text текст сообщения
  582. * @return bool результат отправки
  583. */
  584. public function sendMessage(?User $author, string $text): bool
  585. {
  586. (new Message())->createDialogue($this, $author, $text);
  587.  
  588. return true;
  589. }
  590.  
  591. /**
  592. * Возвращает количество писем пользователя
  593. *
  594. * @return int количество писем
  595. */
  596. public function getCountMessages(): int
  597. {
  598. return Dialogue::query()->where('user_id', $this->id)->count();
  599. }
  600.  
  601. /**
  602. * Возвращает размер контакт-листа
  603. *
  604. * @return int количество контактов
  605. */
  606. public function getCountContact(): int
  607. {
  608. return Contact::query()->where('user_id', $this->id)->count();
  609. }
  610.  
  611. /**
  612. * Возвращает размер игнор-листа
  613. *
  614. * @return int количество игнорируемых
  615. */
  616. public function getCountIgnore(): int
  617. {
  618. return Ignore::query()->where('user_id', $this->id)->count();
  619. }
  620.  
  621. /**
  622. * Возвращает количество записей на стене сообщений
  623. *
  624. * @return int количество записей
  625. */
  626. public function getCountWall(): int
  627. {
  628. return Wall::query()->where('user_id', $this->id)->count();
  629. }
  630.  
  631. /**
  632. * Удаляет альбом пользователя
  633. *
  634. * @return void
  635. */
  636. public function deleteAlbum(): void
  637. {
  638. $photos = Photo::query()->where('user_id', $this->id)->get();
  639.  
  640. if ($photos->isNotEmpty()) {
  641. foreach ($photos as $photo) {
  642. $photo->comments()->delete();
  643. $photo->delete();
  644. }
  645. }
  646. }
  647.  
  648. /**
  649. * Удаляет записи пользователя из всех таблиц
  650. *
  651. * @return bool|null результат удаления
  652. * @throws Exception
  653. */
  654. public function delete(): ?bool
  655. {
  656. deleteFile(HOME . $this->picture);
  657. deleteFile(HOME . $this->avatar);
  658.  
  659. Message::query()->where('user_id', $this->id)->delete();
  660. Contact::query()->where('user_id', $this->id)->delete();
  661. Ignore::query()->where('user_id', $this->id)->delete();
  662. Rating::query()->where('user_id', $this->id)->delete();
  663. Wall::query()->where('user_id', $this->id)->delete();
  664. Note::query()->where('user_id', $this->id)->delete();
  665. Notebook::query()->where('user_id', $this->id)->delete();
  666. Banhist::query()->where('user_id', $this->id)->delete();
  667. Bookmark::query()->where('user_id', $this->id)->delete();
  668. Login::query()->where('user_id', $this->id)->delete();
  669. Invite::query()->where('user_id', $this->id)->orWhere('invite_user_id', $this->id)->delete();
  670.  
  671. return parent::delete();
  672. }
  673.  
  674.  
  675. /**
  676. * Updates count messages
  677. *
  678. * return void
  679. */
  680. public function updatePrivate(): void
  681. {
  682. if ($this->newprivat) {
  683. $countDialogues = Dialogue::query()
  684. ->where('user_id', $this->id)
  685. ->where('reading', 0)
  686. ->count();
  687.  
  688. if ($countDialogues !== $this->newprivat) {
  689. $this->update([
  690. 'newprivat' => $countDialogues,
  691. 'sendprivatmail' => 0,
  692. ]);
  693. }
  694. }
  695. }
  696.  
  697. /**
  698. * Check Access
  699. *
  700. * return void
  701. */
  702. public function checkAccess(): void
  703. {
  704. $request = request();
  705.  
  706. // Banned
  707. if ($this->level === self::BANNED && ! $request->is('ban', 'rules', 'logout')) {
  708. redirect('/ban?user=' . $this->login);
  709. }
  710.  
  711. // Confirm registration
  712. if ($this->level === self::PENDED && setting('regkeys') && ! $request->is('key', 'ban', 'login', 'logout', 'captcha')) {
  713. redirect('/key?user=' . $this->login);
  714. }
  715. }
  716.  
  717. /**
  718. * Getting daily bonus
  719. *
  720. * return void
  721. */
  722. public function gettingBonus(): void
  723. {
  724. if ($this->timebonus < strtotime('-23 hours', SITETIME)) {
  725. $this->increment('money', setting('bonusmoney'));
  726. $this->update(['timebonus' => SITETIME]);
  727.  
  728. setFlash('success', __('main.daily_bonus', ['money' => plural(setting('bonusmoney'), setting('moneyname'))]));
  729. }
  730. }
  731.  
  732. /**
  733. * Сохраняет посещения
  734. *
  735. * @param string $type
  736. *
  737. * @return void
  738. */
  739. public function saveVisit(string $type): void
  740. {
  741. $authorization = Login::query()
  742. ->where('user_id', $this->id)
  743. ->where('created_at', '>', SITETIME - 60)
  744. ->first();
  745.  
  746. if (! $authorization) {
  747. Login::query()->create([
  748. 'user_id' => $this->id,
  749. 'ip' => getIp(),
  750. 'brow' => getBrowser(),
  751. 'created_at' => SITETIME,
  752. 'type' => $type,
  753. ]);
  754. }
  755.  
  756. $this->increment('visits');
  757. }
  758.  
  759. /**
  760. * Remember user
  761. *
  762. * @param User $user
  763. * @param bool $remember
  764. */
  765. private function rememberUser(User $user, bool $remember = false): void
  766. {
  767. if ($remember) {
  768. $options = [
  769. 'expires' => strtotime('+1 year', SITETIME),
  770. 'path' => '/',
  771. 'domain' => siteDomain(siteUrl()),
  772. 'secure' => false,
  773. 'httponly' => true,
  774. 'samesite' => 'Lax',
  775. ];
  776.  
  777. setcookie('login', $user->login, $options);
  778. setcookie('password', md5($user->password . config('app.key')), $options);
  779. }
  780.  
  781. $_SESSION['id'] = $user->id;
  782. $_SESSION['password'] = md5(config('app.key') . $user->password);
  783. $_SESSION['online'] = null;
  784. }
  785. }