singletonIf('users.social', function ($app) { $i = $app->make(static::class); $i->init(); return $i; }); return bff('users.social'); } /** * Получаем ID провайдера по ключу * @param string $providerKey ключ провайдера * @return int 0 если ключ некорректный */ public function getProviderID(string $providerKey): int { $nID = constant('static::PROVIDER_' . mb_strtoupper($providerKey)); return ( ! is_null($nID) ? $nID : 0); } /** * Получаем ключ провайдера по ID * @param int $id ID провайдера * @return string '' если ID был указан некорректно */ public function getProviderKeyByID($id): string { if (is_numeric($id)) { $config = config::file('social'); if (! empty($config['providers']) && is_array($config['providers'])) { foreach ($config['providers'] as $k => $v) { if ($this->getProviderID($k) == $id) { return mb_strtolower($k); } } } } elseif (is_string($id) && !empty($id)) { return $id; } return ''; } /** * Получаем список доступных провайдеров * @return array */ public function getProvidersEnabled(): array { $result = []; $config = Users::socialConfig(); if (! empty($config['providers']) && is_array($config['providers'])) { foreach ($config['providers'] as $k => $v) { if (!empty($v['enabled']) && sizeof($v) > 1) { if (isset($v['keys'])) { unset($v['keys']); } $key = mb_strtolower($k); $v['id'] = $this->getProviderID($key); $v['key'] = $key; $result[$v['id']] = $v; } } } return $result; } /** * Авторизация через HybridAuth * @param string $providerKey ключ провайдера * @return int|\bff\http\Response */ public function auth(string $providerKey) { $providerKey = mb_strtolower($providerKey); $config = Users::socialConfig(['provider' => $providerKey]); try { if (empty($providerKey)) { $sDone = $this->input->get('hauth_done'); $sError = $this->input->get('error'); if (!empty($sDone) && !empty($sError)) { return $this->redirectFromPopup(); } } $auth = new Hybridauth($config); $redirect = $this->input->get('ret', TYPE_STR); if (! empty($redirect) && stripos($redirect, SITEHOST) !== false) { Session::put('users-login-social-redirect', $redirect); } # Вернули $adapter # или # Показали окно авторизации(запрос приложения) соц.сети (exit) $adapter = $auth->authenticate($providerKey); # Уcпешная авторизация $profile = $adapter->getUserProfile(); $providerID = $this->getProviderID($providerKey); $profileID = $profile->identifier; $userID = User::id(); # Ищем соц.аккаунт $data = $this->getSocialAccountData($profileID, $providerID); if (empty($data)) { # Нет соц.аккаунта # Создаем соц.аккаунт $socialID = $this->createSocialAccount(0, $profileID, $providerID, $this->serializeHybridProfile($profile)); if ($socialID) { if ($userID) { # пользователь авторизован => привязываем соц.аккаунт к пользователю $this->linkSocialAccountToUser($socialID, $userID); return $this->redirectFromPopup(); } # сохраняем в сессии пометку о процессе авторизации через соц.сеть $this->authStatus($socialID); # переходим к шагу №2 return $this->redirectFromPopup(Users::url('register', ['step' => 'social'])); } else { # Не удалось создать соц.аккаунт $this->errors->set(_t('users', 'An authorization error occurred, try to refresh the page and Log In again')); } } else { $socialID = $data['id']; if (!$data['user_id']) { # соц.аккаунт не привязан к пользователю сайта if ($userID) { # пользователь авторизован => привяжем соц.аккаунт к пользователю $this->linkSocialAccountToUser($socialID, $userID); return $this->redirectFromPopup(); } # сохраняем в сессии пометку о процессе авторизации через соц.сеть $this->authStatus($socialID); # переходим к шагу №2 return $this->redirectFromPopup(Users::url('register', ['step' => 'social'])); } else { # привязан if ($userID) { # одновременно один профиль в одной соц.сети не может быть привязан # к двум разным аккаунтам пользователей(Users::TABLE_USERS), поэтому проверку на привязку # текущего пользователя к данному соц.аккаунту ($socialID) не выполняем: # - пользователь авторизован, обновляем страницу return $this->redirectFromPopup(); } # Авторизуем пользователя привязанного к данному соц.аккаунту $result = Users::authById($data['user_id'], ['silent' => false]); if ($result === true) { return $this->redirectFromPopup(); } elseif ($result === 1) { # неактивирован # ... выводим сообщение о необходимости Активации аккаунта } elseif (is_array($result)) { # заблокирован # ... выводим сообщение о Блокировке аккаунта и причину } } } } catch (Exception $e) { $errorCode = $e->getCode(); if ($errorCode == 5) { # пользователь нажал "Отмена" return $this->redirectFromPopup(); } $errors = [ # Unspecified error. 0 => 'Неизвестная ошибка.', # Unknown or disabled provider. 1 => 'Ошибка конфигурации Hybriauth.', # Provider not properly configured. 2 => 'Провайдер настроен некорректно.', # Unknown or disabled provider. 3 => 'Неизвестный или неподдерживаемый провайдер.', # Missing provider application credentials. 4 => 'Доступы проложения провайдера настроены некорректно.', # Authentification failed. The user has canceled the authentication or the provider refused the connection. 5 => 'Ошибка аутентификации. Пользователь "отменил" либо провайдер отказал в подключении', # User profile request failed. Most likely the user is not connected to the provider and he should to authenticate again. 6 => 'Запрос к данным профиля пользователя завершился ошибкой. Скорее всего пользователь не подключился к провайдеру и необходима повторная аутентификация', # User not connected to the provider. 7 => 'Пользователь не подключился к провайдеру.', ]; if (isset($errors[$errorCode])) { if (in_array($errorCode, [6, 7])) { if (! empty($adapter)) { $adapter->logout(); } } } $this->log($e->getMessage()); } return 0; } /** * Помечаем / получаем процесс авторизации в сессии * @param int|null $socialID ID соц.аккаунта или NULL(получаем текущий) * @return mixed|void */ public function authStatus($socialID = null) { $key = 'users-login-social-status'; if (is_null($socialID)) { return Session::get($key); } else { Session::put($key, $socialID); } } /** * Получаем данные о процессе авторизации * @return array|bool */ public function authData() { $socialID = $this->authStatus(); do { if (empty($socialID)) { break; } $data = $this->getSocialAccountData($socialID, false); if (empty($data)) { break; } $profileData = (!empty($data['profile_data']) ? func::unserialize($data['profile_data']) : []); unset($data['profile_data']); # Формируем ФИО $name = []; if (!empty($profileData['firstName'])) { $name[] = $profileData['firstName']; } if (!empty($profileData['lastName'])) { $name[] = $profileData['lastName']; } if (!empty($profileData['displayName']) && empty($name)) { $name[] = $profileData['displayName']; } $data['name'] = join(' ', $name); # Аватар $data['avatar'] = (!empty($profileData['photoURL']) ? $profileData['photoURL'] : ''); # E-mail if (!empty($profileData['email']) && $this->input->isEmail($profileData['email'], false)) { $data['email'] = $profileData['email']; } return $data; } while (false); return false; } /** * Завершаем авторизацию / регистрацию через соц.сеть * Привязываем соц.аккаунт к пользователю * @param int $userID ID пользователя * @return bool */ public function authFinish($userID): bool { $socialID = $this->authStatus(); if ($userID && !empty($socialID) && $socialID > 0) { $success = $this->linkSocialAccountToUser($socialID, $userID); if ($success) { $this->authStatus(0); } return $success; } return false; } /** * Выполняем редирект из popup-окна авторизации через соц.сеть * @param string $url * @return \bff\http\Response */ public function redirectFromPopup(string $url = '') { if ($url === '') { $url = Session::get('users-login-social-redirect') ?: Url::route('index'); } $url = addslashes($url); return Response::html('
'); } /** * Получаем данные о соц.аккаунте * @param int $socialID ID соц.аккаунта(Users::TABLE_USERS_SOCIAL) или ID профиля в соц.сети (если $providerID!==false) * @param int|bool $providerID int - ID провайдера (static::PROVIDER_) или FALSE * @return mixed */ protected function getSocialAccountData($socialID, $providerID = false) { if ($providerID !== false) { return $this->db->one_array('SELECT S.* FROM ' . Users::TABLE_USERS_SOCIAL . ' S, ' . Users::TABLE_USERS . ' U WHERE S.provider_id = :providerID AND S.profile_id = :profileID AND S.user_id = U.user_id', [ ':providerID' => $providerID, ':profileID' => (!empty($socialID) ? strval($socialID) : '0'), ]); } return $this->db->one_array('SELECT S.* FROM ' . Users::TABLE_USERS_SOCIAL . ' S WHERE S.id = :socialID', [ ':socialID' => $socialID, ]); } /** * Прикрепляем соц.аккаунт к пользователю * @param int $socialID ID соц.аккаунта(Users::TABLE_USERS_SOCIAL) * @param int $userID ID пользователя * @return bool */ protected function linkSocialAccountToUser(int $socialID, $userID): bool { if ($socialID <= 0 || empty($userID)) { return false; } $res = $this->db->update( Users::TABLE_USERS_SOCIAL, ['user_id' => $userID], ['id' => $socialID, 'user_id' => 0] ); return !empty($res); } /** * Открепляем соц.аккаунт от пользователя * @param int|string $providerID ID провайдера * @param int $userID ID пользователя * @return bool */ public function unlinkSocialAccountFromUser($providerID, $userID): bool { if (empty($providerID) || empty($userID)) { return false; } $res = $this->db->update( Users::TABLE_USERS_SOCIAL, ['user_id' => 0], ['provider_id' => $providerID, 'user_id' => $userID] ); return !empty($res); } /** * Создаем соц.аккаунт * @param int $userID ID пользователя(Users::TABLE_USERS), к которому привязываем данный соц.аккаунт или 0 * @param int $profileID ID профиля в соц.сети * @param int|string $providerID ID провайдера (static::PROVIDER_) * @param array|string $profileData данные профиля полученные из соц.сети * @return int ID соц.акканута (Users::TABLE_USERS_SOCIAL) */ protected function createSocialAccount($userID, $profileID, $providerID, $profileData = []): int { return $this->db->insert( Users::TABLE_USERS_SOCIAL, [ 'user_id' => $userID, 'provider_id' => $providerID, 'profile_id' => strval($profileID), 'profile_data' => (is_array($profileData) ? serialize($profileData) : $profileData), ], 'id' ); } /** * Получаем данные о соц. аккаунтах пользователя * @param int $userID ID пользователя * @param bool $adminView версию для отображения в профиле пользователя в админ. панели * @return array */ public function getUserSocialAccountsData($userID, bool $adminView = false) { if (! empty($userID)) { $data = $this->db->select_key( 'SELECT * FROM ' . Users::TABLE_USERS_SOCIAL . ' WHERE user_id = :id', 'provider_id', [':id' => $userID] ); } if (empty($data)) { $data = []; } if ($adminView) { $configSocial = config::file('social'); foreach ($data as $providerID => &$v) { $v['provider_key'] = ''; if (is_numeric($providerID)) { if (!empty($configSocial['providers']) && is_array($configSocial['providers'])) { foreach ($configSocial['providers'] as $kk => $vv) { if ($this->getProviderID($kk) == $providerID) { $v['provider_key'] = mb_strtolower(strval($providerID)); break; } } } } elseif (is_string($providerID) && !empty($providerID)) { $v['provider_key'] = $providerID; } $v['profile_data'] = func::unserialize($v['profile_data']); $v['profile_url'] = ( !empty($v['profile_data']['profileURL']) ? $v['profile_data']['profileURL'] : ''); } unset($v); } return $data; } /** * Выполняем сериализацию данных профиля соц.сети * @param mixed $profile * @return string */ protected function serializeHybridProfile($profile): string { if (! empty($profile)) { $vars = []; $varTemplate = get_class_vars(get_class($profile)); foreach ($varTemplate as $name => $defaultVal) { $vars[$name] = $profile->$name; } return serialize($vars); } else { return serialize([]); } } }