Advertisement
Guest User

Untitled

a guest
May 3rd, 2017
612
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 17.74 KB | None | 0 0
  1. <?php
  2.  
  3. namespace App\Services;
  4.  
  5. use DB;
  6. use Auth;
  7. use Hash;
  8. use Event;
  9. use Config;
  10. use Exception;
  11. use Intervention\Image\Image;
  12. use Laravel\Socialite\Facades\Socialite;
  13. use Laravel\Socialite\Two\User as SocialiteUser;
  14. use Prettus\Validator\Exceptions\ValidatorException;
  15. use Illuminate\Database\Eloquent\ModelNotFoundException;
  16.  
  17. use App\Models\User;
  18. use App\Models\Country;
  19. use App\Models\UserRole;
  20. use App\Models\UserToken;
  21. use App\Models\SocialLogin;
  22. use App\Models\UserAppToken;
  23. use App\Traits\ReCaptchaTrait;
  24. use App\Validators\UserValidator;
  25. use App\Events\User\UserWasRegistered;
  26. use App\Events\UnexpectedExceptionRised;
  27. use App\Exceptions\TokenExpiredException;
  28. use App\Exceptions\CaptchaFailsException;
  29. use App\Events\User\UserAskedPasswordReset;
  30.  
  31. /**
  32.  * Сервис пользователей
  33.  */
  34. class UserService
  35. {
  36.     // Проверка капчи
  37.     use ReCaptchaTrait;
  38.  
  39.     /**
  40.      * @public User Модель авторизованного пользователя
  41.      */
  42.     public $user;
  43.  
  44.     /**
  45.      * @public UserValidator Валидатор пользователя
  46.      */
  47.     public $validator;
  48.  
  49.     /**
  50.      * @param User|null $user
  51.      * @param UserValidator $userValidator
  52.      */
  53.     public function __construct(User $user = null, UserValidator $userValidator)
  54.     {
  55.         $this->user = $user;
  56.         $this->validator = $userValidator;
  57.     }
  58.  
  59.     /**
  60.      * Создание токена сброса пароля и отправка письма с ним пользователю
  61.      * Если пользователь не найден по почте, вызывается ModelNotFoundException
  62.      *
  63.      * @param array $input Массив входных данных
  64.      * @throws ModelNotFoundException
  65.      */
  66.     public function createResetPasswordToken(array $input)
  67.     {
  68.         $this->validator->passesOrFail($input, UserValidator::RULE_EMAIL);
  69.         $user = User::whereEmail($input['email']);
  70.         $token = UserToken::createResetPassword($user->email);
  71.         // Вызываем событие запроса пользователем сброса пароля
  72.         // Обработка включает в себя отправку письма с токеном сброса
  73.         event(new UserAskedPasswordReset($user, $token));
  74.     }
  75.  
  76.     /**
  77.      * Сброс пароля пользователю на новый по токену, отправленному ему на почту
  78.      * В случае, если токен или пользователь не найден, вызывается ModelNotFoundException
  79.      * Если токен найден, но просрочен, вызывается TokenExpiredException
  80.      *
  81.      * @param string $token Токен сброса пароля
  82.      * @param array $input Массив входных данных
  83.      * @return bool
  84.      * @throws TokenExpiredException
  85.      * @throws ModelNotFoundException
  86.      */
  87.     public function resetPasswordByToken(string $token, array $input)
  88.     {
  89.         $this->validator->passesOrFail($input, UserValidator::RULE_PASSWORD_CHANGE);
  90.         $token = UserToken::findResetPassword($token);
  91.         $user = User::whereEmail($token->email);
  92.         $user->password = Hash::make($input['password']);
  93.         $token->delete();
  94.         return $user->save();
  95.     }
  96.  
  97.     /**
  98.      * Активация пользователя по токену, отправленному ему на почту
  99.      * В случае, если токен или пользователь не найден, вызывается ModelNotFoundException
  100.      *
  101.      * @param string $token
  102.      * @return bool
  103.      * @throws ModelNotFoundException;
  104.      * @throws Exception
  105.      */
  106.     public function activateByToken(string $token)
  107.     {
  108.         $result = null;
  109.         DB::beginTransaction();
  110.         try {
  111.             $token = UserToken::findActivate($token);
  112.             $user = User::whereEmail($token->email);
  113.             // После подтверждения почты считаем юзера доверенным
  114.             $user->assignRole(UserRole::USER);
  115.             $user->createAppToken();
  116.             $token->delete();
  117.             $result = $user->save();
  118.             DB::commit();
  119.             return $result;
  120.         } catch (Exception $e) {
  121.             DB::rollBack();
  122.             if (get_class($e) !== ModelNotFoundException::class) {
  123.                 event(new UnexpectedExceptionRised($e, 'Exception in user activation'));
  124.             }
  125.             throw $e;
  126.         }
  127.     }
  128.  
  129.     /**
  130.      * Регистрация пользователя
  131.      *
  132.      * @param array $input Массив входных данных
  133.      * Обязательные поля: name, email
  134.      * Необязательные: password, nickname, avatar
  135.      * При отсутствии пароля, ему будет присвоена пустая строка.
  136.      * По идее, из-за проверок форм и модели авторизоваться с пустым паролем не получится
  137.      *
  138.      * Пользователю не будут назначены никакие роли
  139.      *
  140.      * @return User
  141.      * @throws CaptchaFailsException
  142.      * @throws ValidatorException
  143.      * @throws Exception Ошибка во время сохранения модели в БД
  144.      */
  145.     public function register(array $input)
  146.     {
  147.         DB::beginTransaction();
  148.         try {
  149.             // Проверяем капчу и входные данные
  150.             $this->checkCaptchaOrFail();
  151.             $this->validator->passesOrFail($input, UserValidator::RULE_CREATE);
  152.             // Создаём нового пользователя
  153.             $user = new User();
  154.             $user->email = $input['email'];
  155.             $user->password = isset($input['password']) ? Hash::make($input['password']) : '';
  156.             $user->name = ucfirst(trim($input['name']));
  157.             if (isset($input['nickname'])) {
  158.                 $user->nickname = $input['nickname'];
  159.             }
  160.             if (isset($input['avatar'])) {
  161.                 $user->avatar = $input['avatar'];
  162.             }
  163.             /** @todo Реализовать работу с разными странами */
  164.             $user->country_id = Country::whereSlug(Country::RUSSIA)->id;
  165.             $user->saveOrFail();
  166.             // Создаём токен активации пользователя
  167.             $token = UserToken::createActivate($user->email);
  168.             // Вызываем событие регистрации пользователя
  169.             // Обработка включает в себя отправку письма с токеном активации
  170.             event(new UserWasRegistered($user, UserWasRegistered::AT_SITE, $token));
  171.             DB::commit();
  172.             return $user;
  173.         } catch (Exception $e) {
  174.             DB::rollBack();
  175.             if (!in_array(get_class($e), [ValidatorException::class, CaptchaFailsException::class])) {
  176.                 event(new UnexpectedExceptionRised($e, 'Exception in user registration'));
  177.             }
  178.             throw $e;
  179.         }
  180.     }
  181.  
  182.     /**
  183.      * Регистрация пользователя через социальную сеть
  184.      * Пользователю будет назначена роль UserRole::USER
  185.      *
  186.      * @param SocialiteUser $socialUser Объект пользователя соцсети
  187.      * @param string $provider Провайдер соцсети
  188.      * @return User|false
  189.      */
  190.     protected function registerSocial(SocialiteUser $socialUser, string $provider)
  191.     {
  192.         // Создаём нового пользователя
  193.         $user = new User();
  194.         $user->email = $this->getSocialEmail($socialUser);
  195.         $user->password = '';
  196.         $user->name = $socialUser->getName();
  197.         $user->avatar = $socialUser->getAvatar();
  198.         /** @todo Реализовать работу с разными странами */
  199.         $user->country_id = Country::whereSlug(Country::RUSSIA)->id;
  200.         $user->saveOrFail();
  201.         // При регистрации через соцсеть считаем юзера сразу доверенным
  202.         $user->assignRole(UserRole::USER);
  203.         $user->createAppToken();
  204.         // Добавляем связь пользователя с соцсетью
  205.         $socialLogin = new SocialLogin();
  206.         $socialLogin->social_id = $socialUser->getId();
  207.         $socialLogin->provider = $provider;
  208.         $user->socialLogins()->save($socialLogin);
  209.         // Вызываем событие регистрации пользователя
  210.         event(new UserWasRegistered($user, UserWasRegistered::WITH_SOCIAL, $socialLogin));
  211.         DB::commit();
  212.         return $user;
  213.     }
  214.  
  215.     /**
  216.      * Редирект пользователя на страницу авторизации соцсети
  217.      *
  218.      * @param string $provider Строковый идентификатор соцсети (vkontakte, facebook,...)
  219.      * @return \Illuminate\Http\Response
  220.      * @throws Exception
  221.      */
  222.     public function socialRedirect(string $provider)
  223.     {
  224.         if (empty(Config::get('services.' . $provider))) {
  225.             throw new Exception('Провайдер авторизации не найден');
  226.         }
  227.         return Socialite::with($provider)->redirect();
  228.     }
  229.  
  230.     /**
  231.      * Авторизация пользователя через социальную сеть
  232.      *
  233.      * @param string $provider Строковый идентификатор соцсети (vkontakte, facebook,...)
  234.      * @throws Exception
  235.      */
  236.     public function loginSocial(string $provider)
  237.     {
  238.         DB::beginTransaction();
  239.         try {
  240.             if (empty(Config::get('services.' . $provider))) {
  241.                 throw new Exception('Socialite provider not found');
  242.             }
  243.             /** @var SocialiteUser $socialUser */
  244.             $socialUser = Socialite::driver($provider)->user();
  245.             // Если пользователь с такой почтой зарегистрирован, выбираем его
  246.             $sameUser = User::whereEmail($this->getSocialEmail($socialUser), false);
  247.             if ($sameUser === null) {
  248.                 // Иначе проверяем, нет ли зарегистрированного пользователя с аккаунтом в соцсети $provider
  249.                 /** @var SocialLogin $sameUserSocialLogin */
  250.                 $sameUserSocialLogin = SocialLogin::whereSocialId($socialUser->getId())
  251.                     ->whereProvider($provider)
  252.                     ->first();
  253.                 // Если нет, то регистрируем по данным соцсети
  254.                 if (empty($sameUserSocialLogin)) {
  255.                     $sameUser = $this->registerSocial($socialUser, $provider);
  256.                 } else {
  257.                     // А если есть, то грузим нового пользователя
  258.                     $sameUser = $sameUserSocialLogin->user;
  259.                 }
  260.             }
  261.             //Так, или иначе, авторизуем полученного пользователя
  262.             Auth::login($sameUser, true);
  263.             DB::commit();
  264.         } catch (Exception $e) {
  265.             DB::rollBack();
  266.             event(new UnexpectedExceptionRised($e, "Exception in user social registration with '$provider'"));
  267.             throw $e;
  268.         }
  269.     }
  270.  
  271.     /**
  272.      * Генерация фейковой почты для соцсетей, которые её не имеют
  273.      *
  274.      * @param SocialiteUser $socialUser
  275.      * @return string
  276.      */
  277.     protected function getSocialEmail(SocialiteUser $socialUser)
  278.     {
  279.         // ok.ru может иметь пользователя без почты. Делаем для таких фейковую
  280.         /** @todo Решить вопрос с доменным именем поизящнее */
  281.         return $socialUser->getEmail() ?: $socialUser->getId() . '@ok.ru';
  282.     }
  283.  
  284.     /**
  285.      * Авторизация пользователя по email и паролю
  286.      * В случае невозможности авторизоваться, или если пользователь не активирован, вызывается исключение
  287.      *
  288.      * @param array $input
  289.      * @throws ValidatorException
  290.      * @throws ModelNotFoundException
  291.      */
  292.     public function login(array $input)
  293.     {
  294.         $this->validator->passesOrFail($input, UserValidator::RULE_LOGIN);
  295.         /* Получаем объект пользователя для авторизации
  296.          * Благодаря собственной реализации провайдера пользователя AuthUserProvider
  297.          * попутно проверяется наличие у него роли UserRole::USER
  298.          *
  299.          * В случае, если нужной роли нет, модель не возвращается, т.е. ситуация аналогична
  300.          * попытке авторизации с неправильным email
  301.          */
  302.         if (!Auth::attempt(
  303.             [
  304.                 'email' => $input['email'],
  305.                 'password' => $input['password']
  306.             ],
  307.             isset($input['remember']) && $input['remember'] === 'on' ? true : false
  308.         )) {
  309.             throw new ModelNotFoundException();
  310.         }
  311.     }
  312.  
  313.     /**
  314.      * Обновление, или генерация и сохранение токена авторизации пользователя
  315.      * через приложение просмотра Tin Can пакетов
  316.      *
  317.      * @return UserAppToken|false Объект токена или false в случае ошибки сохранения
  318.      */
  319.     public function refreshAppToken()
  320.     {
  321.         $fingerprint = $this->user->getFingerprint();
  322.         // Если данный токен уже существует, обновляем и возвращаем его
  323.         if ($token = UserAppToken::find($fingerprint)) {
  324.             $token->touch();
  325.             return $token;
  326.         }
  327.         // Иначе, создаём новый
  328.         return $this->user->createAppToken($fingerprint);
  329.     }
  330.  
  331.     /**
  332.      * Сохранение одного поля профиля
  333.      * Доступны для сохранения: name, nickname, phone, country_id, avatar
  334.      * Для других полей будет вызвано ValidatorException
  335.      * email, естественно, проверяется на валидность
  336.      *
  337.      * @param array $input Массив входных данных
  338.      * @throws ValidatorException
  339.      */
  340.     public function saveProfileField(array $input)
  341.     {
  342.         $this->validator->passesOrFail($input, UserValidator::RULE_PROFILE_SAVE_FIELD);
  343.         if ($input['field'] === 'email') {
  344.             $this->validator->passOrFailEmail($input['value']);
  345.         } elseif ($input['field'] === 'nickname') {
  346.             $this->validator->passOrFailNickname($input['value']);
  347.         }
  348.         $user = Auth::user();
  349.         $user->{$input['field']} = $input['value'];
  350.         $user->saveOrFail();
  351.     }
  352.  
  353.     /**
  354.      * Сохранение профиля
  355.      * Доступны для сохранения: name, nickname, email, phone, country_id
  356.      * Другие поля будут проигнорированы
  357.      *
  358.      * @param array $input Массив входных данных
  359.      * @throws ValidatorException
  360.      */
  361.     public function saveProfile(array $input)
  362.     {
  363.         $this->validator->passesOrFail($input, UserValidator::RULE_PROFILE_SAVE);
  364.         $user = Auth::user();
  365.         $expectedFields = ['name', 'nickname', 'phone', 'country_id'];
  366.         foreach ($expectedFields as $field) {
  367.             $user->$field = $input[$field];
  368.         }
  369.         $user->saveOrFail();
  370.     }
  371.  
  372.     /**
  373.      * Загрузка и сохранение нового аватара
  374.      *
  375.      * @param array $input Массив входных данных
  376.      * @param ImageService $imageService
  377.      * @return Image
  378.      * @throws ValidatorException
  379.      * @throws Exception
  380.      */
  381.     public function uploadAvatar(array $input, ImageService $imageService)
  382.     {
  383.         $this->validator->passesOrFail($input, UserValidator::RULE_PROFILE_UPLOAD_AVATAR);
  384.         $image = $imageService->uploadImage($input['img']);
  385.         if (!$image) {
  386.             throw new Exception('Image was not uploaded');
  387.         }
  388.         $user = Auth::user();
  389.         $user->avatar = $image->filename;
  390.         $user->save();
  391.         return $image;
  392.     }
  393.  
  394.     /**
  395.      * Обрезка и сжатие аватара
  396.      *
  397.      * @param array $input Массив входных данных
  398.      * @param ImageService $imageService
  399.      * @return Image
  400.      * @throws ValidatorException
  401.      * @throws Exception
  402.      */
  403.     public function cropAvatar(array $input, ImageService $imageService)
  404.     {
  405.         $this->validator->passesOrFail($input, UserValidator::RULE_PROFILE_CROP_AVATAR);
  406.         $image = $imageService->cropImage($input);
  407.         if (!$image) {
  408.             throw new Exception('Image was not cropped');
  409.         }
  410.         return $image;
  411.     }
  412.  
  413.     /**
  414.      * Получение языковой локали пользователя ru, en, ua,...
  415.      *
  416.      * @return string
  417.      */
  418.     public function getLocale()
  419.     {
  420.         /** @todo Поддержка языковых предпочтений пользователя */
  421.         return config('app.locale');
  422.     }
  423. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement