Регистрация и авторизация через соц. сети с импортом данных из соц. сетей в расширенный профиль пользователя Seblod

Ширшов Александр
24.10.2012
8969

Относительно небольшой обзор о создании авторизации через социальные сети. Попутно затронуты темы создания программным способом пользователей и материалов. Также из статьи можно получить небольшие знания о том, как Seblod хранит данные о пользователе (ТК User).

Внимание! в статье, местами, есть программирование! Чтобы полностью усвоить материал вам нужно иметь общее представление о архитектуре seblod и joomla. Как хорошо что у нас всех есть форум на котором мы эти представления можем получить.

Интеграция Seblod с социальными сетямиНе так давно моё внимание привлекла новость с joomlaportal.ru о выходе компонента sLogin который позволяет проводить регистрацию и авторизацию пользователей через социальные сети. Функционал актуальный, в текущем проекте как-раз необходим. Но при установке возник ряд вопросов которые мне разрешить не удалось:

  • Регистрация проводилась, но логин и/или ФИО пользователя были Вася Пупкин
  • С com_users компонент работает превосходно! Но вот с com_cck совсем никак.


Можно, конечно, дописать slogin, тем более что у него вроде как есть поддержка плагинов, но я в нём не разобрался и документации никакой не нашел. Поэтому решил искать альтернативные пути (Критиковать можно любой абзац кроме этого).

В процессе поиска я познакомился с рядом сервисов, таких как Loginza, uLogin и.т.п. но они или хотели денег или были ограничены. Или и то и другое. Решено было искать php-библиотеку, которая предоставляет нужный функционал (OAuth1, OAuth2, OpenID). Нашел две таких библиотеки: OmniAuth который на ruby и HybridAuth, про первую особо не выясняя решил использовать вторую. Немного информации по HybridAuth можно найти на Хабре.

Библиотека HybridAuth реализует функционал для формирования и обработки запросов к соц. сетям но требует некоторой настройки. О которых чуть позже.

И так мы имеем Joomla, Seblod, HybridAuth и неуемное желание наладить на собственном сайте авторизацию через социальные сети, постараемся контретизировать задачу и разбить ее на части.

Общий алгоритм простой:

  1. Посылаем запрос в соц. сеть
  2. Проверяем входил ли пользователь через соц сеть раньше (если да то авторизуем его, если нет продолжаем)
  3. Создаем пользователя Joomla (Таблица #__users)
  4. Создаем пользователя Seblod (Таблицы #__content, #__cck_store_form_user, #__cck_core)
  5. Авторизуем пользователя и перенаправляем в личный кабинет.

Здесь стоит оговориться о таблице #__cck_store_form_user - Если взять тип контента User который имеется в штатной комплектации Seblod то доп. поля он будет хранить в ней. На всякий случай напомню что все доп. поля нужно сохранять в [Standart.Users.ИмяПоля].

Сразу добавим поля для хранения идентификаторов соц. сетей.

У меня идентификация проходит по 6 соц. сетям:

  • Google - user_auth_google
  • Facebook - user_auth_facebook
  • Twitter - user_auth_twitter
  • Odnoklassniki - user_auth_odnoklassniki
  • Vkontakte - user_auth_vkontakte
  • Yandex - user_auth_yandex
  • Mailru - user_auth_mailru


В расширенный профиль я добавил только одно поле, user_avatar - тип у него Upload Image. Миниатюры он генерирует в том. случае если их нет, о нём будет рассказано ниже. Вы по желанию можете добавить другие поля.

Структура файлов и папокСоздайте в корневой папке сайта папку auth. Создайте в папке файл auth.php и сюда же поместите библиотеку HybridAuth чтобы получилась структура папок именно такая как на скриншоте.

Каким образом будет это работать в конечно итоге? Пользователь будет переходить по ссылке /auth/auth.php?login=twitter и либо проходить авторизацию, либо регистрироваться, либо не авторизоваться.

Откроем файл auth.php и поместим в него некоторый код:

session_start();
$config = dirname(__FILE__) . '/hybridauth/config.php';
require_once( "hybridauth/Hybrid/Auth.php" );
if (isset($_GET["login"])) {
    try {
        $hybridauth = new Hybrid_Auth($config);
        $adapter = $hybridauth->authenticate($_GET['login']);
        $user_profile = $adapter->getUserProfile();
    } catch (Exception $e) {
        die("got an error! " . $e->getMessage());
    }
}

В данном примере мы подключаем и инициируем библиотеку HybridAuth, создаем адаптер (если простым языком то адаптер это вспомогательный класс для работы с конкретной социальной сетью) и получаем данные о пользователе.
В конструктор адаптера мы передаем переменную $_GET[‘login’]. Так делать нельзя, если вы будете так делать то вас взломают, придумайте какую-нибудь проверку для этой входящей переменной. В идеале через in_array().
Практически во всех социальных сетях для получения доступа к их API необходима регистрация своего сайта и получение идентификационных данных (ключ, секретный код). Инструкцию по получению ключей некоторых соц. сетей вы можете найти на страничке FAQ компонента slogin

В папке /auth/hybridauth/ есть файл config.php - именно в нем вам и нужно сохранить эти данные. Файл конфигурации имеет весьма простую структуру, изначально в нем есть конфигурации некоторых адаптеров и вам нужно только подставить значение ключей. Не забудьте отключить неиспользуемые адаптеры установив у них значение enabled в false.

Если вы правильно заполнили файл конфигурации, то при обращении к /auth/auth.php?login=twitter (вместо twitter как вы догадались можно использовать любой другой адаптер) вы получите в переменную $user_profile данные о пользователе которые социальная сеть готова предоставить. Разные соц. сети предоставляют данные с разными именами переменных. Mail.ru к примеру предоставляет nick пользователя а google.com нет. Большинство соц. сетей предоставляет email пользователя и twitter нет.
В HybridAuth есть класс который занимается формализацией данных и он существенно облегчает жизнь. Посмотреть на него можно в файле /auth/hybridauth/Hybrid/User_Profile.php но я предлагаю вам пойти другим путем и самим получать переменные которые нам нужны. Это удобнее всего делать в классе адаптера которые находятся в /auth/hybridauth/Hybrid/Providers.

Для начала определимся - а что же нам нужно. Ведь большинство данных которые нам предоставляет социальная сеть мы никак не используем! Приведем конкретный список.

  • name - Имя пользователя (ФИО), необязательно. Сохраняется в #__users.name
  • username - Логин, обязательно, сохраняется в #__users.username
  • email - Email, обязательно, сохраняется в #__users.email
  • user_avatar - поле из моего расширенного профиля, необязательно, сохраняется в #__cck_store_form_user

А так-же нам нужны идентификаторы пользователей, по которым мы их после регистрации будем авторизировать.

Рассмотрим конкретный пример Google. Его адаптер хранится в файле /auth/hybridauth/Hybrid/Google.php

Структура у адаптеров простая я думаю что вы без труда разберетесь что в нём зачем. Нас интересует метод getUserProfile(). После получения данных и проверок идет заполнение переменной $this->user->profile которую данный метод и возвращает.
Перед первым присвоением вы можете положить пару строк кода:

var_dump($response);
die();

… и посмотреть какие данные предоставляет нам Google.

Идентификатор Google я храню в user_auth_google - поле Seblod которое уже упоминалось выше. Для каждой соц. сети я создаю свое поле, чтобы пользователь мог привязать свой аккаунт к любой и соц. сетей, и поэтому ввел две вспомогательные переменные.

$this->user->profile->user_auth_google =
        (property_exists($response, 'id')) ? $response->id : "";
$this->user->profile->auth_field = 'user_auth_google';

Так как Google не предоставляет логина, пришлось пойти на маленькую хитрость:

if ((property_exists($response,'email')))
{
$this->user->profile->email = $response->email;
   	$username = explode('@', $this->user->profile->email);
   	$this->user->profile->username = $username[0];
}

И так для каждой социальной сети которую я использую. Как я использую доп. поля (auth_field) я расскажу ниже.

Думаю что с запросами в социальные сети и ответами от них вопросов нет. Давайте сохранять пользователей.

Как мы определили ранее сохранение пользователей будет в три этапа, а если более подробнее то в 5.

  1. Создаем пользователя
  2. Создаем доп. профиль пользователя
  3. Создаем контент
  4. Связываем пользователя, доп. профиль и контент через cck_core
  5. Связываем контент с доп. профилем и пользователем через cck_core


Но для начала мы подготовим среду.

Создавать пользователя и материалы будем через механизмы Joomla. А для этого нам нужно подгузить joomla framework. Делается это легко и просто.

define('_JEXEC', 1);
define('JPATH_BASE', $_SERVER['DOCUMENT_ROOT']);
define('DS', DIRECTORY_SEPARATOR);
require_once ( JPATH_BASE . DS . 'includes' . DS . 'defines.php' );
require_once ( JPATH_BASE . DS . 'includes' . DS . 'framework.php' );
$mainframe = & JFactory::getApplication('site');
$mainframe->initialise();
$db = & JFactory::getDbo();

Нужно проверить, предоставила ли соц. сеть идентификатор пользователя, к тому-же эта проверка поможет вам не забыть о том что для каждого нового адаптера нужно добавлять в профиль поле auth_field.

if (empty($user_profile->auth_field))
die('Перепишите провайдер добавив в него необходимые поля');

Присваиваем переменным значения:

$field_key = $user_profile->auth_field;
$field_value = $user_profile->$field_key;


В $field_key у нас имя Seblod поля в которое должно быть сохранено $field_value - идентификатор пользователя.

Проверим были ли зарегистрирован пользователь через соц. сеть или нет. (в нашем случае пока нет, так как скрипт не дописан, но после того как мы его напишем все встанет на свои места).

$sql = 'SELECT `id` FROM `#__cck_store_form_user` WHERE `' . $field_key . '` = "' . $field_value . '";';
$db->setQuery($sql);
if (!$db->query())
    die('Что-то пошло не так как ожидалось...');
$user_id = $db->loadResult();

В результате мы в переменной $user_id имеем или id пользователя или NULL если данного идентификатора данной социальной сети у пользователей не оказалось. В этом случае регистрируем пользователя.

if (!$user_id)
{
	// Собственно тут регистрация.
}

Перейдем непосредственно к созданию пользователя Joomla (#__users).

// Создаем нового пользователя
$user = JFactory::getUser(0); 
// it's important to set the "0" otherwise your admin user information will be loaded
// Определяем группы в которые поместить пользователя
jimport('joomla.application.component.helper');
// this part of the snippet from here: /plugins/user/joomla/joomla.php
$config = JComponentHelper::getParams('com_users');
// Default to Registered.
$defaultUserGroup = $config->get('new_usertype', 2);
// Генерируем любой пароль
$password = substr(md5(microtime()), rand(0, 24), rand(6, 8));
$data = array(// массив с настройками пользователей
    'name' => $user_profile->name, // ФИО пользователя
    'username' => $user_profile->username, // Логин пользователя
    'email' => $user_profile->email, // Email пользователя
    'groups' => array($defaultUserGroup), // Группы пользователя
    'password' => $password, // Пароль
    'password2' => $password, // Подтверждение пароля
    'sendEmail' => 0, // should the user receive system mails?
    'block' => 0, // Небудем блокировать пользователя
);
if (!$user->bind($data)) { // now bind the data to the JUser Object, if it not works....
    echo $user->getError(); // ...raise an Warning
    die();
}
if (!$user->save()) { // if the user is NOT saved...
    echo $user->getError(); // ...raise an Warning
    die(); // if you're in a method/function return false
}
//Сохраняем переменную для дальнейшего использования		 
$user_id = $user->id;

В результате мы имеем id пользователя или ошибку. Теперь нам надо создать поля расширенного профиля Joomla. У меня оно одно - это avatar, который предоставляется соц. сетями в виде ссылки. Также мне нужно сохранить уникальный идентификатор пользователя для той соц. сети с помощью которой он авторизовался. Так как поле с аватаром у меня Upload Image картинку я буду сохранять, а превью будут создаваться сами (см. настройки поля).

$user_avatar = '';
if (!empty($user_profile->user_avatar)) {
    if (!is_dir(JPATH_BASE . DS . 'images' . DS . 'avatars' . DS . $user_id))
        mkdir(JPATH_BASE . DS . 'images' . DS . 'avatars' . DS . $user_id);
    $ext = array_pop(explode('.', $user_profile->user_avatar));
    $legal_ext = array('jpg', 'jpeg', 'gif', 'png');
    if (in_array($ext, $legal_ext)) {
        $avatar_path = JPATH_BASE . DS . 'images' . DS . 'avatars' . DS . $user_id . DS . 'avatar.' . $ext;
        file_put_contents($avatar_path, file_get_contents($user_profile->user_avatar));
        $user_avatar = str_replace($_SERVER['DOCUMENT_ROOT'] . DS, '', $avatar_path);
    }
}
// Добавляем дополнительные данные о пользователе
$sql = 'INSERT INTO `#__cck_store_form_user` (`id`, `user_avatar`, `' . $field_key . '`)
VALUES (\'' . $user_id . '\', “' . $user_avatar . '”, “' . $field_value . '”)';
$db->setQuery($sql);
if (!$db->query()) {
    echo 'Не удалось сохранить поля расширенного профиля';
    $db->setQuery('DELETE FROM `#__users` WHERE `id` = ' . $user_id);
    $db->query();
    die();
}

Думаю что код в комментариях не нуждается, загружаем аватарку, сохраняем запись в таблицу. Если сохранить не получилось то удаляем ранее созданного пользователя чтобы не висел мусором в базе.

Теперь нужно создать материал, чтобы пользователя можно было редактировать через формы себлода. Процедура очень похожа на создание пользователя.

// Создаем материал
$content = JTable::getInstance('Content', 'JTable', array());
$data = array(
    'catid' => 41, // ID категории
    'title' => $user_profile->name,
    'alias' => $user_profile->username,
    'introtext' => '::cck::[после создания записи в cck_core эту запись перезатрем]::/cck::',
    'fulltext' => '',
    'state' => 1,
    'created_by' => $user_id,
    'modified_by' => $user_id
);
// Bind data
if (!$content->bind($data)) {
    $db->setQuery('DELETE FROM `#__users` WHERE `id` = ' . $user_id);
    $db->query();
    $db->setQuery('DELETE FROM `#__cck_store_form_user` WHERE `id` = ' . $user_id);
    $db->query();
    echo $content->getError();
    die();
}
// Check the data.
if (!$content->check()) {
    $db->setQuery('DELETE FROM `#__users` WHERE `id` = ' . $user_id);
    $db->query();
    $db->setQuery('DELETE FROM `#__cck_store_form_user` WHERE `id` = ' . $user_id);
    $db->query();
    echo $content->getError();
    die();
}
if (!$content->store()) {
// если вдруг добавление материала не увеначалось успехом )))
    $db->setQuery('DELETE FROM `#__users` WHERE `id` = ' . $user_id);
    $db->query();
    $db->setQuery('DELETE FROM `#__cck_store_form_user` WHERE `id` = ' . $user_id);
    $db->query();
    echo 'Добавление материала не увенчалось успехом!';
    die();
}
$content_id = $content->id;

В случае ошибки незабываем подчищать хвосты. Кстати обратите внимание на introtext, его мы следующим этапом заменим. В introtext храниться ссылка на id записи из таблицы #__cck_core, поэтому сразу её добавить не получится. Внимание! В примере я сохраняю материал в категорию с ID = 41! Не забудьте поменять на свою.

Осталось связать все эти записи, обновить introtext в #__content и полноценный Seblod пользователь будет создан.
Связываем

// Добавляем связь в cck_core
$sql = 'INSERT INTO `#__cck_core` (`cck`, `pk`, `pkb`, `storage_location`, `author_id`)
VALUES ("user", ' . $user_id . ', ' . $content_id . ', "joomla_user", ' . $user_id . ');';
$db->setQuery($sql);
if (!$db->query()) {
    $db->setQuery('DELETE FROM `#__users` WHERE `id` = ' . $user_id);
    $db->query();
    $db->setQuery('DELETE FROM `#__cck_store_form_user` WHERE `id` = ' . $user_id);
    $db->query();
    $db->setQuery('DELETE FROM `#__content` WHERE `id` = ' . $content_id);
    $db->query();
    echo 'Не удалось добавить запись в cck_core';
    die();
}
$cck_id = $db->insertid();

Обновляем introtext

$sql = 'UPDATE #__content SET introtext = "::cck::' . $cck_id . '::/cck::" WHERE `id`=' . $content_id . ';';
$db->setQuery($sql);
if (!$db->query()) {
    $db->setQuery('DELETE FROM `#__users` WHERE `id` = ' . $user_id);
    $db->query();
    $db->setQuery('DELETE FROM `#__cck_store_form_user` WHERE `id` = ' . $user_id);
    $db->query();
    $db->setQuery('DELETE FROM `#__content` WHERE `id` = ' . $content_id);
    $db->query();
    $db->setQuery('DELETE FROM `#__cck_core` WHERE `id` = ' . $cck_id);
    $db->query();
    echo 'Не удалось присвоить материалу id cck';
    die();
}

Теперь полноценный Seblod пользователь готов. Вы еще не забыли что у нас он создавался в условии (!$user_id) ? Теперь вне этого блока проводим авторизацию пользователя и редирект в собственный новоиспеченный профиль.

// авторизовываем пользователя
$user = JUser::getInstance($user_id);
$session = JFactory::getSession();
// если getInstance вернуло ошибку
if ($user instanceof Exception) {
    echo 'instance instanceof Exception';
    die();
}
// Если пользователь заблокирован
if ($user > get('block') == 1) {
    echo JText::_('JERROR_NOLOGIN_BLOCKED');
    die();
}
$user->set('guest', 0);
$user->set('usertype', 'deprecated');
$session->set('user', $user);
// Check to see the the session already exists.
$mainframe->checkSession();
// Заносим запись в таблицу с сессиями
$db->setQuery(
        'UPDATE ' . $db->quoteName('#__session') .
        ' SET ' . $db->quoteName('guest') . ' = ' . $db->quote($instance->get('guest')) . ',' .
        '    ' . $db->quoteName('username') . ' = ' . $db->quote($instance->get('username')) . ',' .
        '    ' . $db->quoteName('userid') . ' = ' . (int) $instance->get('id') .
        ' WHERE ' . $db->quoteName('session_id') . ' = ' . $db->quote($session->getId())
);
$db->query();
$user > setLastVisit();
// Отправляем пользователя в свой, новоиспеченный профиль
$mainframe->redirect('/profile');

Если пользователь авторизовался в первый раз, то создается профиль и происходит авторизация. Если пользователь снова авторизовался то профиль уже создаваться не будет.

Вы можете добавить в свой расширенный профиль ссылки на привязку к аккаунтам, а в этом файле добавить условие - проверять авторизованный пользователь выполняет скрипт или нет?

Цель данной статьи именно познакомить вас с существующими механизмами но не в коем случае не предоставить готовое решение, так как в этой статье, также как и во многих других подобных статьях, вопросы фильтрации переменных остались за бортом.

Спасибо за внимание.


Используемые источники:

AJAX в Joomla 2.5
http://habrahabr.ru/post/145488/

HybridAuth — интеграция сайта с социальными сетями
http://habrahabr.ru/post/149187/

… функции добавления контента-статей в joomla?
http://joomla-support.ru/thread24041.html

Joomla 2.5 new registered user php script with dbl opt-in java http://stackoverflow.com/questions/10035826/joomla-2-5-new-registered-user-php-script-with-dbl-opt-in-java

 

Комментарии  

# Алексей Марцинкевич Спасибо за актуальный материал! Надо будет попробовать

У Вас недостаточно прав для добавления комментариев. Для того, чтобы оставить свой комментарий необходимо зарегистрироваться на сайте.