Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- namespace App\Services;
- use DB;
- use Auth;
- use Hash;
- use Event;
- use Config;
- use Exception;
- use Intervention\Image\Image;
- use Laravel\Socialite\Facades\Socialite;
- use Laravel\Socialite\Two\User as SocialiteUser;
- use Prettus\Validator\Exceptions\ValidatorException;
- use Illuminate\Database\Eloquent\ModelNotFoundException;
- use App\Models\User;
- use App\Models\Country;
- use App\Models\UserRole;
- use App\Models\UserToken;
- use App\Models\SocialLogin;
- use App\Models\UserAppToken;
- use App\Traits\ReCaptchaTrait;
- use App\Validators\UserValidator;
- use App\Events\User\UserWasRegistered;
- use App\Events\UnexpectedExceptionRised;
- use App\Exceptions\TokenExpiredException;
- use App\Exceptions\CaptchaFailsException;
- use App\Events\User\UserAskedPasswordReset;
- /**
- * Сервис пользователей
- */
- class UserService
- {
- // Проверка капчи
- use ReCaptchaTrait;
- /**
- * @public User Модель авторизованного пользователя
- */
- public $user;
- /**
- * @public UserValidator Валидатор пользователя
- */
- public $validator;
- /**
- * @param User|null $user
- * @param UserValidator $userValidator
- */
- public function __construct(User $user = null, UserValidator $userValidator)
- {
- $this->user = $user;
- $this->validator = $userValidator;
- }
- /**
- * Создание токена сброса пароля и отправка письма с ним пользователю
- * Если пользователь не найден по почте, вызывается ModelNotFoundException
- *
- * @param array $input Массив входных данных
- * @throws ModelNotFoundException
- */
- public function createResetPasswordToken(array $input)
- {
- $this->validator->passesOrFail($input, UserValidator::RULE_EMAIL);
- $user = User::whereEmail($input['email']);
- $token = UserToken::createResetPassword($user->email);
- // Вызываем событие запроса пользователем сброса пароля
- // Обработка включает в себя отправку письма с токеном сброса
- event(new UserAskedPasswordReset($user, $token));
- }
- /**
- * Сброс пароля пользователю на новый по токену, отправленному ему на почту
- * В случае, если токен или пользователь не найден, вызывается ModelNotFoundException
- * Если токен найден, но просрочен, вызывается TokenExpiredException
- *
- * @param string $token Токен сброса пароля
- * @param array $input Массив входных данных
- * @return bool
- * @throws TokenExpiredException
- * @throws ModelNotFoundException
- */
- public function resetPasswordByToken(string $token, array $input)
- {
- $this->validator->passesOrFail($input, UserValidator::RULE_PASSWORD_CHANGE);
- $token = UserToken::findResetPassword($token);
- $user = User::whereEmail($token->email);
- $user->password = Hash::make($input['password']);
- $token->delete();
- return $user->save();
- }
- /**
- * Активация пользователя по токену, отправленному ему на почту
- * В случае, если токен или пользователь не найден, вызывается ModelNotFoundException
- *
- * @param string $token
- * @return bool
- * @throws ModelNotFoundException;
- * @throws Exception
- */
- public function activateByToken(string $token)
- {
- $result = null;
- DB::beginTransaction();
- try {
- $token = UserToken::findActivate($token);
- $user = User::whereEmail($token->email);
- // После подтверждения почты считаем юзера доверенным
- $user->assignRole(UserRole::USER);
- $user->createAppToken();
- $token->delete();
- $result = $user->save();
- DB::commit();
- return $result;
- } catch (Exception $e) {
- DB::rollBack();
- if (get_class($e) !== ModelNotFoundException::class) {
- event(new UnexpectedExceptionRised($e, 'Exception in user activation'));
- }
- throw $e;
- }
- }
- /**
- * Регистрация пользователя
- *
- * @param array $input Массив входных данных
- * Обязательные поля: name, email
- * Необязательные: password, nickname, avatar
- * При отсутствии пароля, ему будет присвоена пустая строка.
- * По идее, из-за проверок форм и модели авторизоваться с пустым паролем не получится
- *
- * Пользователю не будут назначены никакие роли
- *
- * @return User
- * @throws CaptchaFailsException
- * @throws ValidatorException
- * @throws Exception Ошибка во время сохранения модели в БД
- */
- public function register(array $input)
- {
- DB::beginTransaction();
- try {
- // Проверяем капчу и входные данные
- $this->checkCaptchaOrFail();
- $this->validator->passesOrFail($input, UserValidator::RULE_CREATE);
- // Создаём нового пользователя
- $user = new User();
- $user->email = $input['email'];
- $user->password = isset($input['password']) ? Hash::make($input['password']) : '';
- $user->name = ucfirst(trim($input['name']));
- if (isset($input['nickname'])) {
- $user->nickname = $input['nickname'];
- }
- if (isset($input['avatar'])) {
- $user->avatar = $input['avatar'];
- }
- /** @todo Реализовать работу с разными странами */
- $user->country_id = Country::whereSlug(Country::RUSSIA)->id;
- $user->saveOrFail();
- // Создаём токен активации пользователя
- $token = UserToken::createActivate($user->email);
- // Вызываем событие регистрации пользователя
- // Обработка включает в себя отправку письма с токеном активации
- event(new UserWasRegistered($user, UserWasRegistered::AT_SITE, $token));
- DB::commit();
- return $user;
- } catch (Exception $e) {
- DB::rollBack();
- if (!in_array(get_class($e), [ValidatorException::class, CaptchaFailsException::class])) {
- event(new UnexpectedExceptionRised($e, 'Exception in user registration'));
- }
- throw $e;
- }
- }
- /**
- * Регистрация пользователя через социальную сеть
- * Пользователю будет назначена роль UserRole::USER
- *
- * @param SocialiteUser $socialUser Объект пользователя соцсети
- * @param string $provider Провайдер соцсети
- * @return User|false
- */
- protected function registerSocial(SocialiteUser $socialUser, string $provider)
- {
- // Создаём нового пользователя
- $user = new User();
- $user->email = $this->getSocialEmail($socialUser);
- $user->password = '';
- $user->name = $socialUser->getName();
- $user->avatar = $socialUser->getAvatar();
- /** @todo Реализовать работу с разными странами */
- $user->country_id = Country::whereSlug(Country::RUSSIA)->id;
- $user->saveOrFail();
- // При регистрации через соцсеть считаем юзера сразу доверенным
- $user->assignRole(UserRole::USER);
- $user->createAppToken();
- // Добавляем связь пользователя с соцсетью
- $socialLogin = new SocialLogin();
- $socialLogin->social_id = $socialUser->getId();
- $socialLogin->provider = $provider;
- $user->socialLogins()->save($socialLogin);
- // Вызываем событие регистрации пользователя
- event(new UserWasRegistered($user, UserWasRegistered::WITH_SOCIAL, $socialLogin));
- DB::commit();
- return $user;
- }
- /**
- * Редирект пользователя на страницу авторизации соцсети
- *
- * @param string $provider Строковый идентификатор соцсети (vkontakte, facebook,...)
- * @return \Illuminate\Http\Response
- * @throws Exception
- */
- public function socialRedirect(string $provider)
- {
- if (empty(Config::get('services.' . $provider))) {
- throw new Exception('Провайдер авторизации не найден');
- }
- return Socialite::with($provider)->redirect();
- }
- /**
- * Авторизация пользователя через социальную сеть
- *
- * @param string $provider Строковый идентификатор соцсети (vkontakte, facebook,...)
- * @throws Exception
- */
- public function loginSocial(string $provider)
- {
- DB::beginTransaction();
- try {
- if (empty(Config::get('services.' . $provider))) {
- throw new Exception('Socialite provider not found');
- }
- /** @var SocialiteUser $socialUser */
- $socialUser = Socialite::driver($provider)->user();
- // Если пользователь с такой почтой зарегистрирован, выбираем его
- $sameUser = User::whereEmail($this->getSocialEmail($socialUser), false);
- if ($sameUser === null) {
- // Иначе проверяем, нет ли зарегистрированного пользователя с аккаунтом в соцсети $provider
- /** @var SocialLogin $sameUserSocialLogin */
- $sameUserSocialLogin = SocialLogin::whereSocialId($socialUser->getId())
- ->whereProvider($provider)
- ->first();
- // Если нет, то регистрируем по данным соцсети
- if (empty($sameUserSocialLogin)) {
- $sameUser = $this->registerSocial($socialUser, $provider);
- } else {
- // А если есть, то грузим нового пользователя
- $sameUser = $sameUserSocialLogin->user;
- }
- }
- //Так, или иначе, авторизуем полученного пользователя
- Auth::login($sameUser, true);
- DB::commit();
- } catch (Exception $e) {
- DB::rollBack();
- event(new UnexpectedExceptionRised($e, "Exception in user social registration with '$provider'"));
- throw $e;
- }
- }
- /**
- * Генерация фейковой почты для соцсетей, которые её не имеют
- *
- * @param SocialiteUser $socialUser
- * @return string
- */
- protected function getSocialEmail(SocialiteUser $socialUser)
- {
- // ok.ru может иметь пользователя без почты. Делаем для таких фейковую
- /** @todo Решить вопрос с доменным именем поизящнее */
- return $socialUser->getEmail() ?: $socialUser->getId() . '@ok.ru';
- }
- /**
- * Авторизация пользователя по email и паролю
- * В случае невозможности авторизоваться, или если пользователь не активирован, вызывается исключение
- *
- * @param array $input
- * @throws ValidatorException
- * @throws ModelNotFoundException
- */
- public function login(array $input)
- {
- $this->validator->passesOrFail($input, UserValidator::RULE_LOGIN);
- /* Получаем объект пользователя для авторизации
- * Благодаря собственной реализации провайдера пользователя AuthUserProvider
- * попутно проверяется наличие у него роли UserRole::USER
- *
- * В случае, если нужной роли нет, модель не возвращается, т.е. ситуация аналогична
- * попытке авторизации с неправильным email
- */
- if (!Auth::attempt(
- [
- 'email' => $input['email'],
- 'password' => $input['password']
- ],
- isset($input['remember']) && $input['remember'] === 'on' ? true : false
- )) {
- throw new ModelNotFoundException();
- }
- }
- /**
- * Обновление, или генерация и сохранение токена авторизации пользователя
- * через приложение просмотра Tin Can пакетов
- *
- * @return UserAppToken|false Объект токена или false в случае ошибки сохранения
- */
- public function refreshAppToken()
- {
- $fingerprint = $this->user->getFingerprint();
- // Если данный токен уже существует, обновляем и возвращаем его
- if ($token = UserAppToken::find($fingerprint)) {
- $token->touch();
- return $token;
- }
- // Иначе, создаём новый
- return $this->user->createAppToken($fingerprint);
- }
- /**
- * Сохранение одного поля профиля
- * Доступны для сохранения: name, nickname, phone, country_id, avatar
- * Для других полей будет вызвано ValidatorException
- * email, естественно, проверяется на валидность
- *
- * @param array $input Массив входных данных
- * @throws ValidatorException
- */
- public function saveProfileField(array $input)
- {
- $this->validator->passesOrFail($input, UserValidator::RULE_PROFILE_SAVE_FIELD);
- if ($input['field'] === 'email') {
- $this->validator->passOrFailEmail($input['value']);
- } elseif ($input['field'] === 'nickname') {
- $this->validator->passOrFailNickname($input['value']);
- }
- $user = Auth::user();
- $user->{$input['field']} = $input['value'];
- $user->saveOrFail();
- }
- /**
- * Сохранение профиля
- * Доступны для сохранения: name, nickname, email, phone, country_id
- * Другие поля будут проигнорированы
- *
- * @param array $input Массив входных данных
- * @throws ValidatorException
- */
- public function saveProfile(array $input)
- {
- $this->validator->passesOrFail($input, UserValidator::RULE_PROFILE_SAVE);
- $user = Auth::user();
- $expectedFields = ['name', 'nickname', 'phone', 'country_id'];
- foreach ($expectedFields as $field) {
- $user->$field = $input[$field];
- }
- $user->saveOrFail();
- }
- /**
- * Загрузка и сохранение нового аватара
- *
- * @param array $input Массив входных данных
- * @param ImageService $imageService
- * @return Image
- * @throws ValidatorException
- * @throws Exception
- */
- public function uploadAvatar(array $input, ImageService $imageService)
- {
- $this->validator->passesOrFail($input, UserValidator::RULE_PROFILE_UPLOAD_AVATAR);
- $image = $imageService->uploadImage($input['img']);
- if (!$image) {
- throw new Exception('Image was not uploaded');
- }
- $user = Auth::user();
- $user->avatar = $image->filename;
- $user->save();
- return $image;
- }
- /**
- * Обрезка и сжатие аватара
- *
- * @param array $input Массив входных данных
- * @param ImageService $imageService
- * @return Image
- * @throws ValidatorException
- * @throws Exception
- */
- public function cropAvatar(array $input, ImageService $imageService)
- {
- $this->validator->passesOrFail($input, UserValidator::RULE_PROFILE_CROP_AVATAR);
- $image = $imageService->cropImage($input);
- if (!$image) {
- throw new Exception('Image was not cropped');
- }
- return $image;
- }
- /**
- * Получение языковой локали пользователя ru, en, ua,...
- *
- * @return string
- */
- public function getLocale()
- {
- /** @todo Поддержка языковых предпочтений пользователя */
- return config('app.locale');
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement