Аутентификация через Twitter

Привет, друзья! Да, да, да, наконец дошли руки до долгожданного урока, о котором просили многие из вас: реализации аутентификации через Twitter.

sourse

В прошлом году на нашем сайте был выложен целый ряд статей, направленных на раскрытие процессов аутентификации пользователей через различные социальные сети и сервисы. Тогда же поступило огромное множество просьб о том, чтобы мы показали как сделать то же самое через Twitter. Загвоздка заключалась в том, что данный сервис для аутентификации использовал и использует технологию oAuth1, в то время как все остальные перешли на oAuth2. Видя что Twitter не торопится переходить на новую технологию по сей день, я всё-таки решил сделать урок по текущему состоянию технологий.

Предыдущие уроки по этой теме:

Заметка: некоторые из вас совершенно справедливо могут заметить, что Twitter частично поддерживает oAuth2, однако по данной схеме можно только осуществлять запросы к API, но не аутентифицировать пользователей.

Теперь, давайте приступим к делу и посмотрим на то, как всё-таки осуществить аутентификацию через Twitter. Готовьтесь! Нас ждёт множество шагов!

Шаг 1. Создание приложения

В первую очередь, вам необходимо создать новое Twitter приложение на сайте разработчиков (для логина можете использовать данные от вашего twitter аккаунта).

Для создания нового приложения нужно ввести следующие поля:

  • Название приложения (Name)
  • Описание (Description)
  • Название вашего веб сайта (Website)
  • Адрес куда будет отправлен пользователь после аутентификации (Callback URL)

В качестве адреса сайта и колбэка, я ввёл "http://twitterauth.loc/". Дело в том, что данное поле валидируется на правильность формата, поэтому значения типа "http://localhost/twitter-auth" не подойдут. Хост для данного названия мы создадим на следующем шаге. Если вы хотите пробовать сразу на вашем существующем сайте, то введите его название. В дальнейшем все эти данные могут быть изменены во вкладке (settings).

После создания приложения для вас будут доступны следующие данные:

  • Детальная информация о приложении.

  • Специальные ключи приложения, секретные коды и так далее, которые вы сможете найти во вкладке API Keys.

  • Права приложения, которые будут запрашиваться у пользователя. Все возможные варианты

    Возможные варианты: “Читать твиты из вашей ленты”, “Видеть, кого вы читаете.”, "Читать новых людей.", "Обновлять ваш профиль.", "Публиковать твиты от вашего имени.", "Открыть доступ к вашим личным сообщениям." …

Шаг 2. Создание виртуального хоста

Для создания виртуального хоста на Windows, вам необходимо создать запись в файле “каталог_apache\conf\extra\httpd-vhosts.conf”:

<VirtualHost *:80>
   ServerName twitterauth.loc
   DocumentRoot "каталог_apache/htdocs/twitter-oauth"

   <Directory "каталог_apache/htdocs/twitter-oauth">
       DirectoryIndex index.php
       AllowOverride All
       Order allow,deny
       Allow from all
   </Directory>
</VirtualHost>

Так же, необходимо внести запись в файл хостов Windows C:\Windows\System32\Drivers\etc\hosts:

127.0.0.1 twitterauth.loc

Перезапустите Apache. Зайдите по адресу http://twitterauth.loc/. Если всё сделали правильно, то в результате вы должны увидеть содержимое каталога каталог_apache/htdocs/twitter-oauth, где мы дальше будем работать.

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

Шаг 3. Определяем базовые настройки приложения

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

define('CONSUMER_KEY', '3AMDpQZ9wjJboX3LpMFQ');
define('CONSUMER_SECRET', 'iKkE344c5dEcIJXjZqXkCyd7nckIXFvOC1GUMTZa0o');

// адрес получения токена запроса
define('REQUEST_TOKEN_URL', 'https://api.twitter.com/oauth/request_token');
// адрес аутентификации
define('AUTHORIZE_URL', 'https://api.twitter.com/oauth/authorize');
// адрес получения токена доступа
define('ACCESS_TOKEN_URL', 'https://api.twitter.com/oauth/access_token');
// адрес API получения информации о пользователе
define('ACCOUNT_DATA_URL', 'https://api.twitter.com/1.1/users/show.json');

// колбэк, адрес куда должен будет перенаправлен пользователь, после аутентификации
define('CALLBACK_URL', 'http://twitterauth.loc/');

Шаг 4. Генерации подписи для запроса получения токена запроса (request token)

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

//определим разделитель, который отделять параметры друг от друга
define('URL_SEPARATOR', '&');

Для создания подписи и адреса запроса нам понадобятся следующие параметры:

// хэш случайной строки
$oauth_nonce = md5(uniqid(rand(), true));

// текущее время
$oauth_timestamp = time();

Далее формируем набор параметров в строгой последовательности. Последовательность и кодирование определённых моментов действительно очень важна. В противном случае, запрос может не пройти:

// формируем набор параметров
$params = array(
    'oauth_callback=' . urlencode(CALLBACK_URL) . URL_SEPARATOR,
    'oauth_consumer_key=' . CONSUMER_KEY . URL_SEPARATOR,
    'oauth_nonce=' . $oauth_nonce . URL_SEPARATOR,
    'oauth_signature_method=HMAC-SHA1' . URL_SEPARATOR,
    'oauth_timestamp=' . $oauth_timestamp . URL_SEPARATOR,
    'oauth_version=1.0'
);

// склеиваем все параметры, применяя к каждому из них функцию urlencode
$oauth_base_text = implode('', array_map('urlencode', $params));

// специальный ключ
$key = CONSUMER_SECRET . URL_SEPARATOR;

// формируем общий текст строки
$oauth_base_text = 'GET' . URL_SEPARATOR . urlencode(REQUEST_TOKEN_URL) . URL_SEPARATOR . $oauth_base_text;

// хэшируем с помощью алгоритма sha1
$oauth_signature = base64_encode(hash_hmac('sha1', $oauth_base_text, $key, true));

В результате у вас должно получиться значение типа: 5OeIu3Q8asBEweb9N3slIQ8L/jc=

Шаг 5. Формирование токена запроса (request token)

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

// готовим массив параметров
$params = array(
    URL_SEPARATOR . 'oauth_consumer_key=' . CONSUMER_KEY,
    'oauth_nonce=' . $oauth_nonce,
    'oauth_signature=' . urlencode($oauth_signature),
    'oauth_signature_method=HMAC-SHA1',
    'oauth_timestamp=' . $oauth_timestamp,
    'oauth_version=1.0'
);

// склеиваем параметры для формирования url
$url = REQUEST_TOKEN_URL . '?oauth_callback=' . urlencode(CALLBACK_URL) . implode('&', $params);

// Отправляем GET запрос по сформированному url
$response = file_get_contents($url);

// Парсим ответ
parse_str($response, $response);

// записываем ответ в переменные
$oauth_token = $response['oauth_token'];
$oauth_token_secret = $response['oauth_token_secret'];

В результате вы должны получить ответ вида:

oauth_token=udOL7svu5AdR49WAT5IfNB0cteo2OvNuZhHJbnqZLto&oauth_token_secret=jHlR6pcIL7dOlZmB7tGb8YC9TOyuWAeoDcuRFvVw&oauth_callback_confirmed=true

С помощью функции parse_str, преобразуем данную строку в массив и записываем в переменные $oauth_token и $oauth_token_secret. Если вы получили другой ответ, то значит отправили неверные данные.

Шаг 6. Формируем ссылку для аутентификации

$link = AUTHORIZE_URL . '?oauth_token=' . $oauth_token;

echo '<a href="' . $link . '">Аутентификация через Twitter</a>';

Шаг 7. Формирование подписи для получения токена доступа

Теперь, когда у нас есть токен запроса и секрет токена запроса, самое время перейти к следующему шагу и получить, так называемый, токен доступа, с помощью которого получим доступ к данным пользователя. Но перед тем как получить сам токен доступа, сначала нужно сформировать подпись следующего запроса к twitter-у. Процесс предстоит тот же. Внимание! Весь следующий код будем писать в блоке:

if (!empty($_GET['oauth_token']) && !empty($_GET['oauth_verifier'])) {
    // далее всё пишем тут
}

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

$oauth_nonce = md5(uniqid(rand(), true));
$oauth_timestamp = time();

// получаем oauth_token пришедший после перенаправления от Twitter-а
$oauth_token = $_GET['oauth_token'];
// получаем oauth_verifier пришедший после перенаправления от Twitter-а
$oauth_verifier = $_GET['oauth_verifier'];


$oauth_base_text = "GET&";
$oauth_base_text .= urlencode(ACCESS_TOKEN_URL)."&";

$params = array(
    'oauth_consumer_key=' . CONSUMER_KEY . URL_SEPARATOR,
    'oauth_nonce=' . $oauth_nonce . URL_SEPARATOR,
    'oauth_signature_method=HMAC-SHA1' . URL_SEPARATOR,
    'oauth_token=' . $oauth_token . URL_SEPARATOR,
    'oauth_timestamp=' . $oauth_timestamp . URL_SEPARATOR,
    'oauth_verifier=' . $oauth_verifier . URL_SEPARATOR,
    'oauth_version=1.0'
);

$key = CONSUMER_SECRET . URL_SEPARATOR . $oauth_token_secret;
$oauth_base_text = 'GET' . URL_SEPARATOR . urlencode(ACCESS_TOKEN_URL) . URL_SEPARATOR . implode('', array_map('urlencode', $params));
$oauth_signature = base64_encode(hash_hmac("sha1", $oauth_base_text, $key, true));

Шаг 8. Формируем строку параметров для получения токена доступа

Теперь, когда у нас есть подпись нового запроса, самое время сформировать саму строку этого запроса:

$params = array(
    'oauth_nonce=' . $oauth_nonce,
    'oauth_signature_method=HMAC-SHA1',
    'oauth_timestamp=' . $oauth_timestamp,
    'oauth_consumer_key=' . CONSUMER_KEY,
    'oauth_token=' . urlencode($oauth_token),
    'oauth_verifier=' . urlencode($oauth_verifier),
    'oauth_signature=' . urlencode($oauth_signature),
    'oauth_version=1.0'
);
$url = ACCESS_TOKEN_URL . '?' . implode('&', $params);

$response = file_get_contents($url);
parse_str($response, $response);

Если вы сделали всё правильно, то в результате должны получить строку вида:

oauth_token=962149290-hpnbdORejotxrAuWTnCFI8gXhdp88Hv4J7KUH47s&oauth_token_secret=KlhUFBc7bsJvrsbMrIEa8XjiWfrA4iOeGlaFTdhcWS4V5&user_id=962149290&screen_name=stanislasprime

Шаг 9. Формируем подпись для запроса получения данных о пользователе

Приступаем к последней группе шагов. Осталось совсем немного. Теперь когда у нас есть токен доступа, можем получить данные о пользователе, который пытается аутентифицироваться. Для этого нужно отправить ещё один запрос, но перед этим, по уже сложившейся традиции, сформируем подпись данного запроса:

$oauth_nonce = md5(uniqid(rand(), true));
$oauth_timestamp = time();

$oauth_token = $response['oauth_token'];
$oauth_token_secret = $response['oauth_token_secret'];
$screen_name = $response['screen_name'];

$params = array(
    'oauth_consumer_key=' . CONSUMER_KEY . URL_SEPARATOR,
    'oauth_nonce=' . $oauth_nonce . URL_SEPARATOR,
    'oauth_signature_method=HMAC-SHA1' . URL_SEPARATOR,
    'oauth_timestamp=' . $oauth_timestamp . URL_SEPARATOR,
    'oauth_token=' . $oauth_token . URL_SEPARATOR,
    'oauth_version=1.0' . URL_SEPARATOR,
    'screen_name=' . $screen_name
);
$oauth_base_text = 'GET' . URL_SEPARATOR . urlencode(ACCOUNT_DATA_URL) . URL_SEPARATOR . implode('', array_map('urlencode', $params));

$key = CONSUMER_SECRET . '&' . $oauth_token_secret;
$signature = base64_encode(hash_hmac("sha1", $oauth_base_text, $key, true));

Шаг 10. Получаем данные о пользователе

Вот он, последний шаг, которые отделяет нас от пользовательских данных. Снова формируем набор параметров для формирования запроса:

$params = array(
    'oauth_consumer_key=' . CONSUMER_KEY,
    'oauth_nonce=' . $oauth_nonce,
    'oauth_signature=' . urlencode($signature),
    'oauth_signature_method=HMAC-SHA1',
    'oauth_timestamp=' . $oauth_timestamp,
    'oauth_token=' . urlencode($oauth_token),
    'oauth_version=1.0',
    'screen_name=' . $screen_name
);

$url = ACCOUNT_DATA_URL . '?' . implode(URL_SEPARATOR, $params);

$response = file_get_contents($url);

// преобразуем json в массив
$user_data = json_decode($response);

Если вы сделали всё правильно, то в результате должны получить подобный ответ:

array (size=42)
  'id' => int 962149290
  'id_str' => string '962149290' (length=9)
  'name' => string 'StanislaS Prime' (length=15)
  'screen_name' => string 'stanislasprime' (length=14)
  'location' => string 'Кишинёв-Одесса' (length=27)
  'description' => string '' (length=0)
  'url' => string 'http://t.co/q7Tswagq8q' (length=22)
  'entities' =>
    array (size=2)
      'url' =>
        array (size=1)
          'urls' =>
            array (size=1)
              ...
      'description' =>
        array (size=1)
          'urls' =>
            array (size=0)
              ...
  'protected' => boolean false
  'followers_count' => int 2
  'friends_count' => int 7
  'listed_count' => int 0
  'created_at' => string 'Wed Nov 21 10:32:55 +0000 2012' (length=30)
  'favourites_count' => int 0
  'utc_offset' => int 10800
  'time_zone' => string 'Kyiv' (length=4)
  'geo_enabled' => boolean false
  'verified' => boolean false
  'statuses_count' => int 4
  'lang' => string 'ru' (length=2)
  'status' =>
    array (size=22)
      'created_at' => string 'Wed Mar 12 20:33:25 +0000 2014' (length=30)
      'id' => float 4.4328406871875E+17
      'id_str' => string '443784255087618230' (length=18)
      'text' => string '' (length=230)
      'source' => string 'web' (length=3)
      'truncated' => boolean false
      'in_reply_to_status_id' => null
      'in_reply_to_status_id_str' => null
      'in_reply_to_user_id' => null
      'in_reply_to_user_id_str' => null
      'in_reply_to_screen_name' => null
      'geo' => null
      'coordinates' => null
      'place' => null
      'contributors' => null
      'retweet_count' => int 0
      'favorite_count' => int 0
      'entities' =>
        array (size=5)
          'hashtags' =>
            array (size=0)
              ...
          'symbols' =>
            array (size=0)
              ...
          'urls' =>
            array (size=0)
              ...
          'user_mentions' =>
            array (size=0)
              ...
          'media' =>
            array (size=1)
              ...
      'favorited' => boolean false
      'retweeted' => boolean false
      'possibly_sensitive' => boolean false
      'lang' => string 'ru' (length=2)
  'contributors_enabled' => boolean false
  'is_translator' => boolean false
  'is_translation_enabled' => boolean false
  'profile_background_color' => string 'C0DEED' (length=6)
  'profile_background_image_url' => string 'http://abs.twimg.com/images/themes/theme1/bg.png' (length=48)
  'profile_background_image_url_https' => string 'https://abs.twimg.com/images/themes/theme1/bg.png' (length=49)
  'profile_background_tile' => boolean false
  'profile_image_url' => string 'http://pbs.twimg.com/profile_images/437485860859023360/voDNO_oY_normal.jpeg' (length=75)
  'profile_image_url_https' => string 'https://pbs.twimg.com/profile_images/437485860859023360/voDNO_oY_normal.jpeg' (length=76)
  'profile_link_color' => string '0084B4' (length=6)
  'profile_sidebar_border_color' => string 'C0DEED' (length=6)
  'profile_sidebar_fill_color' => string 'DDEEF6' (length=6)
  'profile_text_color' => string '333333' (length=6)
  'profile_use_background_image' => boolean true
  'default_profile' => boolean true
  'default_profile_image' => boolean false
  'following' => boolean false
  'follow_request_sent' => boolean false
  'notifications' => boolean false
  'suspended' => boolean false
  'needs_phone_verification' => boolean false

Полный листинг

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

// определяем изначальные конфигурационные данные

define('CONSUMER_KEY', '3AMDpQZ9wjJboX3LpMFQ');
define('CONSUMER_SECRET', 'iKkE344c5dEcIJXjZqXkCyd7nckIXFvOC1GUMTZa0o');

define('REQUEST_TOKEN_URL', 'https://api.twitter.com/oauth/request_token');
define('AUTHORIZE_URL', 'https://api.twitter.com/oauth/authorize');
define('ACCESS_TOKEN_URL', 'https://api.twitter.com/oauth/access_token');
define('ACCOUNT_DATA_URL', 'https://api.twitter.com/1.1/users/show.json');

define('CALLBACK_URL', 'http://twitterauth.loc/');


// формируем подпись для получения токена доступа
define('URL_SEPARATOR', '&');

$oauth_nonce = md5(uniqid(rand(), true));
$oauth_timestamp = time();

$params = array(
    'oauth_callback=' . urlencode(CALLBACK_URL) . URL_SEPARATOR,
    'oauth_consumer_key=' . CONSUMER_KEY . URL_SEPARATOR,
    'oauth_nonce=' . $oauth_nonce . URL_SEPARATOR,
    'oauth_signature_method=HMAC-SHA1' . URL_SEPARATOR,
    'oauth_timestamp=' . $oauth_timestamp . URL_SEPARATOR,
    'oauth_version=1.0'
);

$oauth_base_text = implode('', array_map('urlencode', $params));
$key = CONSUMER_SECRET . URL_SEPARATOR;
$oauth_base_text = 'GET' . URL_SEPARATOR . urlencode(REQUEST_TOKEN_URL) . URL_SEPARATOR . $oauth_base_text;
$oauth_signature = base64_encode(hash_hmac('sha1', $oauth_base_text, $key, true));


// получаем токен запроса
$params = array(
    URL_SEPARATOR . 'oauth_consumer_key=' . CONSUMER_KEY,
    'oauth_nonce=' . $oauth_nonce,
    'oauth_signature=' . urlencode($oauth_signature),
    'oauth_signature_method=HMAC-SHA1',
    'oauth_timestamp=' . $oauth_timestamp,
    'oauth_version=1.0'
);
$url = REQUEST_TOKEN_URL . '?oauth_callback=' . urlencode(CALLBACK_URL) . implode('&', $params);

$response = file_get_contents($url);
parse_str($response, $response);

$oauth_token = $response['oauth_token'];
$oauth_token_secret = $response['oauth_token_secret'];


// генерируем ссылку аутентификации

$link = AUTHORIZE_URL . '?oauth_token=' . $oauth_token;

echo '<a href="' . $link . '">Аутентификация через Twitter</a>';


if (!empty($_GET['oauth_token']) && !empty($_GET['oauth_verifier'])) {
    // готовим подпись для получения токена доступа

    $oauth_nonce = md5(uniqid(rand(), true));
    $oauth_timestamp = time();
    $oauth_token = $_GET['oauth_token'];
    $oauth_verifier = $_GET['oauth_verifier'];


    $oauth_base_text = "GET&";
    $oauth_base_text .= urlencode(ACCESS_TOKEN_URL)."&";

    $params = array(
        'oauth_consumer_key=' . CONSUMER_KEY . URL_SEPARATOR,
        'oauth_nonce=' . $oauth_nonce . URL_SEPARATOR,
        'oauth_signature_method=HMAC-SHA1' . URL_SEPARATOR,
        'oauth_token=' . $oauth_token . URL_SEPARATOR,
        'oauth_timestamp=' . $oauth_timestamp . URL_SEPARATOR,
        'oauth_verifier=' . $oauth_verifier . URL_SEPARATOR,
        'oauth_version=1.0'
    );

    $key = CONSUMER_SECRET . URL_SEPARATOR . $oauth_token_secret;
    $oauth_base_text = 'GET' . URL_SEPARATOR . urlencode(ACCESS_TOKEN_URL) . URL_SEPARATOR . implode('', array_map('urlencode', $params));
    $oauth_signature = base64_encode(hash_hmac("sha1", $oauth_base_text, $key, true));

    // получаем токен доступа
    $params = array(
        'oauth_nonce=' . $oauth_nonce,
        'oauth_signature_method=HMAC-SHA1',
        'oauth_timestamp=' . $oauth_timestamp,
        'oauth_consumer_key=' . CONSUMER_KEY,
        'oauth_token=' . urlencode($oauth_token),
        'oauth_verifier=' . urlencode($oauth_verifier),
        'oauth_signature=' . urlencode($oauth_signature),
        'oauth_version=1.0'
    );
    $url = ACCESS_TOKEN_URL . '?' . implode('&', $params);

    $response = file_get_contents($url);
    parse_str($response, $response);


    // формируем подпись для следующего запроса
    $oauth_nonce = md5(uniqid(rand(), true));
    $oauth_timestamp = time();

    $oauth_token = $response['oauth_token'];
    $oauth_token_secret = $response['oauth_token_secret'];
    $screen_name = $response['screen_name'];

    $params = array(
        'oauth_consumer_key=' . CONSUMER_KEY . URL_SEPARATOR,
        'oauth_nonce=' . $oauth_nonce . URL_SEPARATOR,
        'oauth_signature_method=HMAC-SHA1' . URL_SEPARATOR,
        'oauth_timestamp=' . $oauth_timestamp . URL_SEPARATOR,
        'oauth_token=' . $oauth_token . URL_SEPARATOR,
        'oauth_version=1.0' . URL_SEPARATOR,
        'screen_name=' . $screen_name
    );
    $oauth_base_text = 'GET' . URL_SEPARATOR . urlencode(ACCOUNT_DATA_URL) . URL_SEPARATOR . implode('', array_map('urlencode', $params));

    $key = CONSUMER_SECRET . '&' . $oauth_token_secret;
    $signature = base64_encode(hash_hmac("sha1", $oauth_base_text, $key, true));

    // получаем данные о пользователе
    $params = array(
        'oauth_consumer_key=' . CONSUMER_KEY,
        'oauth_nonce=' . $oauth_nonce,
        'oauth_signature=' . urlencode($signature),
        'oauth_signature_method=HMAC-SHA1',
        'oauth_timestamp=' . $oauth_timestamp,
        'oauth_token=' . urlencode($oauth_token),
        'oauth_version=1.0',
        'screen_name=' . $screen_name
    );

    $url = ACCOUNT_DATA_URL . '?' . implode(URL_SEPARATOR, $params);

    $response = file_get_contents($url);
    $user_data = json_decode($response, true);
    var_dump($user_data);
}

Итог

Фуууух, вот это мы дали жару! Вот такой вот непростой процесс создания аутентификации через Twitter. Ждём ваших отзывов. В случае если у вас что-то не выходит, 100 раз убедитесь, что все изначальные данные верны и хост настроен верно.

Так же совет. В процессе разработки не нажимайте F5 для обновления страницы, т.к. полученные токены могут устаревать и полезет куча ошибок. Если вам нужно пере-аутентифицироваться, то нажимайте на соответствующую ссылку.

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

Данный урок подготовлен для вас командой сайта ruseller.com
Источник урока: www.ruseller.com
Автор: Станислав Протасевич
Урок создан: 13 Апреля 2014
Просмотров: 29269
Правила перепечатки


5 последних уроков рубрики "PHP"

  • Фильтрация данных с помощью zend-filter

    Когда речь идёт о безопасности веб-сайта, то фраза "фильтруйте всё, экранируйте всё" всегда будет актуальна. Сегодня поговорим о фильтрации данных.

  • Контекстное экранирование с помощью zend-escaper

    Обеспечение безопасности веб-сайта — это не только защита от SQL инъекций, но и протекция от межсайтового скриптинга (XSS), межсайтовой подделки запросов (CSRF) и от других видов атак. В частности, вам нужно очень осторожно подходить к формированию HTML, CSS и JavaScript кода.

  • Подключение Zend модулей к Expressive

    Expressive 2 поддерживает возможность подключения других ZF компонент по специальной схеме. Не всем нравится данное решение. В этой статье мы расскажем как улучшили процесс подключение нескольких модулей.

  • Совет: отправка информации в Google Analytics через API

    Предположим, что вам необходимо отправить какую-то информацию в Google Analytics из серверного скрипта. Как это сделать. Ответ в этой заметке.

  • Подборка PHP песочниц

    Подборка из нескольких видов PHP песочниц. На некоторых вы в режиме online сможете потестить свой код, но есть так же решения, которые можно внедрить на свой сайт.

^ Наверх ^