vorst.ru - Статьи о задачах возникающих при создании сайта и их решении
Статьи помеченные widget

Как подключить авторизацию через аккаунт

OAuth2 авторизация с использованием аккаунта

Допустим, что вы уже зарегистрированы в известной социальной сети. То есть вы уже сообщили о себе, все, что считали нужным. Очевидно вам не хочется проходить процедуру еще раз на другом ресурсе.

Авторизация с помощью социальной сети позволяет избежать повторного заполнения формы регистрации. При этом защищенность вашего аккаунта в социальной сети никак не страдает. Вы просто подтверждаете, что вы это вы. Как подключить эту услугу к проекту используя расширение yiisoft/yii2-authclient?


Как написать виджет для WordPress

Пример виджета для WordPress

Иногда производители предоставляют возможность покупателю самостоятельно выбрать на сайте комплектацию товара. В этом случае, при разработке сайта, нужно предусмотреть возможность показа текущей цены на видном месте (demo).

В WordPress для подобных задач используется область виджетов. Как писать виджет в WordPress подробно написано в кодексе. Но всегда есть нюансы.


Как написать short-code для WordPress

Пример написания short-code для WordPress

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

Должна быть возможность легко размещать такой вопросник в статье или на странице блога WordPress. Об этом была речь в предыдущем посте.


Как написать плагин для WordPress

Плагин для WordPress подсчета цены по компонентам

Любой товар состоит из элементов. Довольно часто производитель, поставив задачу написать электронный магазин, позволяет выбирать комплектацию заранее, до производства. Выбирая комплектующие есть возможность сэкономить или наоборот, согласиться с утверждением "Я этого достоин(а)" и заказать по полной.

Например можно представить пиццерию, где в меню указана стоимость составляющих - лепешка, сыр трех сортов, помидоры двух сортов, колбаса 5 видов. Комбинируете вы сами и постоянно видите, как ваш выбор отражается на цене.

Но это отвлеченный пример, а был конкретный - Demo, Plugin.


Виджет для вывода списка рубрик Nested Set

Выбор всех статей по рубрике

В конце предыдущей статьи были указаны две вещи, которых не хватает для начала использования рубрикатора.

Первая из них - необходимо добавить поле rubric в таблицу post. С этой операцией связано и изменения в модели common/models/Post, которые очевидно касаются методов rules() и attributeLabels().

Кроме этого нужно добавить обработку выбранной пользователем рубрики в actionIndex() контроллера frontend/controllers/PostController.



Поиск



Как подключить авторизацию через аккаунт

OAuth2 авторизация с использованием аккаунта

Допустим, что вы уже зарегистрированы в известной социальной сети. То есть вы уже сообщили о себе, все, что считали нужным. Очевидно вам не хочется проходить процедуру еще раз на другом ресурсе.

Авторизация с помощью социальной сети позволяет избежать повторного заполнения формы регистрации. При этом защищенность вашего аккаунта в социальной сети никак не страдает. Вы просто подтверждаете, что вы это вы. Как подключить эту услугу к проекту используя расширение yiisoft/yii2-authclient?


    Поделиться

Когда пользователь пытается войти на сайт с помощью стороннего сервиса происходит запрос данных пользователя на этом сервисе. Данные возвращаются в объекте client.

Стандартный запрос возвращает имя пользователя, а вот email может уже и не передать. В социальной сети вы можете установить флаг в настройках - "Передавать email сторонним сервисам" (или подобный флаг). Но не факт, что так поступят все посетители вашего сайта. И это первая проблема.

Вторая в том, что названия возвращаемых атрибутов разные для разных социальных сетей.

Инкапсуляция получения данных

Можно написать класс common/components/SocialContact.php, в конструктор которого передается объект client. Конструктор определяет, от какой социальной сети вернулся объект и, в зависимости от этого, формирует данные.

Если email не передан, то генерируется нечто похожее на email. Это необходимо для выполнения в дальнейшем процедуры регистрации пользователя.

namespace common\components;
use Yii;
use yii\base\BaseObject;
use yii\base\InvalidValueException;
use common\models\SocialLink;
use common\models\User;
class SocialContact extends BaseObject
{
  public $id;
  public $name;
  public $email;
  /**
   * Retrieve id, name, email and, may be more.
   * 
   * @param object social client
   * @param array config
   */
  public function __construct($client, $config = [])
  {
    $client_id = $client->getId();
    $attributes = $client->getUserAttributes();
    $this->id = (string)$attributes['id'];
    switch ($client_id) {
    case 'yandex' :
      $this->name = $attributes['first_name'] . ' ' . 
        $attributes['last_name'];
      $this->email = isset($attributes['default_email']) 
        ? $attributes['default_email'] 
        : false;
      break;
    case 'facebook' :
      $this->name = $attributes['name'];
      $this->email = isset($attributes['email']) 
        ? $attributes['email'] 
        : false;
      break;
    // cases for other clients
    }
    // if email not setted then make it
    if (!$this->email)
      $this->email = "{$attributes['id']}@{$client_id}.net";
    parent::__construct($config);
  }

Связь между User и социальной сетью

Получив объект от стороннего сервиса и приведя данные к единому виду нужно добавить нового пользователя. При этом новому пользователю присваивается случайный пароль. В дальнейшем имя и пароль будут использоваться для входа. Пользователь никогда не узнает свой пароль и даже может не подозревать, что и email используется не его, а сгенерированный. Почему, тем не менее, все будет работать верно?

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

Пользователь может быть зарегистрирован в разных социальных сетях, поэтому нужна связь между социальной сетью и аккаунтом пользователя на вашем сайте. Создадим модель common/models/SocialLink.php. Где source это "yandex", "vkontakte" и прочее, а source_id это внутренний код социальной сети.

<?php
namespace common\models;
use yii\db\ActiveRecord;
use common\models\User;
class SocialLink extends ActiveRecord
{
  public static function tableName()
  {
    return '{{%social_link}}';
  }
  public function rules()
  {
    return [
      [['user_id', 'source', 'source_id'], 'required'],
      ['user_id', 'integer'],
      [['source', 'source_id'], 'string', 'max'=>255],
    ];
  }
  public function getUser()
  {
    return User::findOne($this->user_id);
  }
}

И таблицу.

<?php
use yii\db\Migration;
class m180608_123330_create_social_link extends Migration
{
  public $table = '{{%social_link}}';
  public function up()
  {
    $tableOptions = null;
    if ($this->db->driverName === 'mysql') {
      $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
    }
    $this->createTable($this->table, [
      'id' => $this->primaryKey(),
      'user_id' => $this->integer()->notNull(),
      'source' => $this->string(255)->notNull(),
      'source_id' => $this->string(255)->notNull(),
    ], $tableOptions);
    $this->addForeignKey ('FK_social_link_user', 
      $this->table, 'user_id', '{{%user}}', 'id', 'CASCADE');
  }
  public function down()
  {
    $this->dropTable($this->table);
  }
}

User registration

Добавим процедуру регистрации в класс common/components/SocialContact.php. Если пользователь пытается войти с помощью социальной сети, а он уже прошел процедуру обычной регистрации, то выдаем сообщение, что надо бы использовать логин и пароль, указанные при регистрации. Иначе создаем нового пользователя и связываем его с социальной сетью. Если что-то не получается, выдаем сообщение об ошибке.

class SocialContact extends BaseObject
{
  ...
  public function registration($client_id)
  {
    if (User::find()->where(['email' => $this->email])->exists()) {
      Yii::$app->getSession()->setFlash('error', [
        Yii::t('app', 'User with {email} for {client} have been exist, ' .
          'but not linked to each other. Try to login with name and password.', [
          'email' => $this->email,
          'client' => $client_id,
        ]),
      ]);
    } else {
      $password = Yii::$app->security->generateRandomString(6);
      $user = new User([
        'name' => $this->name,
        'email' => $this->email,
        'password' => $password,
        'status' => User::STATUS_ACTIVE,
      ]);
      $user->generateAuthKey();
      $user->generatePasswordResetToken();
      $transaction = $user->getDb()->beginTransaction();
      if ($user->save()) {
        $social_link = new SocialLink([
          'user_id' => $user->id,
          'source' => $client_id,
          'source_id' => $this->id,
        ]);
        if ($social_link->save()) {
          $transaction->commit();
          Yii::$app->user->login($user);
        } else {
          throw new InvalidValueException($this->showErrors($social_link)); 
        }
      } else {
        throw new InvalidValueException($this->showErrors($user)); 
      }
    }
  }
  private function showErrors($model)
  {
    $out = 'Can\'t save ' . $model->tableName() . "\n";
    foreach($model->getErrors() as $field => $messages) {
      $out .= "«{$field}»\n";
      foreach($messages as $message) {
        $out .= "{$message}\n";
      }
      $out .= "\n";
    }
    return $out;
  }
}

Controller

Действие в контроллере frontend/controllers/SiteController.php.

class SiteController extends Controller
{
  public function actions()
  {
    return [
      'auth' => [
        'class' => 'yii\authclient\AuthAction',
        'successCallback' => [$this, 'onAuthSuccess'],
      ],
    ],
  }
  public function onAuthSuccess($client)
  {
    $social_contact = new SocialContact($client);
    $social_link = SocialLink::find()->where([
      'source' => $client->getId(),
      'source_id' => $social_contact->id,
    ])->one();
    if (Yii::$app->user->isGuest) {
      if ($social_link) { // authorization
        Yii::$app->user->login($social_link->user);
      } else { // registration
        $social_contact->registration($client->getId());
      }
    } else { // the user is already registered
      if (!$social_link) { // add external service of authentification
        $social_link = new SocialLink([
          'user_id' => Yii::$app->user->id,
          'source' => $client->getId(),
          'source_id' => $social_contact->id,
        ]);
        $social_link->save();
      }
    }
  }

Регистрация приложений

Когда отправляется запрос стороннему сервису (социальной сети), с просьбой сообщить данные пользователя, можно представить, что ответ присылает приложение. Приложения создаются в каждой социальной сети по разному. Например для сети ВКонтакте смотрите здесь. Получив два параметра - идентификатор и секретный ключ приложения, нужно зарегистрировать их в конфигурации common/config/main.php. По Id и секрету приложение определяет, что ваш сайт имеет право на запрос данных пользователя.

<?php
return [
  'components' => [
    'authClientCollection' => [
      'class' => 'yii\authclient\Collection',
      'clients' => [
        'yandex' => [
          'class' => 'yii\authclient\clients\Yandex',
          'clientId' => 'yandex application id',
          'clientSecret' => 'yandex client secret string',
        ],
        'vkontakte' => [
          'class' => 'yii\authclient\clients\VKontakte',
          'clientId' => 'vk application id',
          'clientSecret' => 'vk client secret string',
        ],

Widget

Осталось разместить ссылки в форме входа. Наряду с обычным вариантом входа на сайт, предложить вход с помощью ссылок на социальные сети. Те, что зарегистрированы в файле конфигурации. Ссылки размещает виджет frontend/widgets/SocialCredentials.php, используя информацию о зарегистрированных приложениях.

<?php 
namespace frontend\widgets; 
use yii\base\Widget; 
class SocialCredentials extends Widget 
{ 
  public $view = 'socialCredentials'; 
  public $credentials = []; 
  public $icons = []; 
  public function init() { 
    parent::init(); 
    if(!$this->credentials) 
      $this->credentials = \Yii::$app->get('authClientCollection')->clients; 
    if(!$this->icons) 
      $this->icons = \Yii::$app->params['icons']; 
  } 
  public function run() 
  { 
    echo $this->render($this->view, [ 
      'credentials' => $this->credentials, 
      'icons' => $this->icons, 
    ]); 
  } 
}

Ссылки удобно выводить с помощью иконок. Например Font Awesome. Можно определить соответствие социальных сетей и иконок в параметрах при вызове виджета или в файле frontend/config/params.php.

<?php   
return [
'icons' => [ 
    'yandex' => 'yandex', 
    'vkontakte' => 'vk', 
    'github' => 'github', 
    'google' => 'google-plus-g', 
  ],

Вариант представления frontend/widgets/views/socialCredentials.php используемый на данном сайте.

<?php
use yii\helpers\Url;
?>
<p><?= \Yii::t('app', 'You can log in using the social network.') ?></p>
<ul class="list-inline">
<?php foreach($credentials as $id => $client): ?>
  <span class="fa-stack fa-lg">
    <li class="fa fa-circle fa-stack-1x">
      <a href="<?= Url::to(['site/auth', 'authclient' => $id]) ?>" 
        title="<?= \Yii::t('app', 'Login with') . ' ' . ucfirst($client->id) ?>">
        <i class="fa fa-circle fa-stack-2x"></i>
        <i class="fab fa-<?= $icons[$id] ?> fa-stack-1x fa-inverse"></i>
      </a>
    </li>
  </span>
<?php endforeach; ?>
</ul>

Как это работает на данном сайте.

Заключение

В конструкторе класса common/components/SocialContacts.php присваиваются значения переменным, в зависимости от социальной сети. Если возникнет необходимость добавить еще одну, не определенную еще, социальную сеть, то придется переписывать класс. Попробуем переписать конструктор так, чтобы новые возможности не приводили к изменениям. Но об этом в следующей статье.

При подготовке статьи использовано руководство Quick start расширения yiisoft/yii2-authclient. Код, приведенный в статье, используется в расширении sergmoro1/yii2-user.

Leave a comment

Only authorized users can leave comments. Please log in or pass a registration.