(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Работа с Gmail используя PHP

Источник: habrahabr
mixkorshun

Доброго времени суток, коллеги. В этой статье я расскажу об опыте использовании Gmail API. Как оказалось, данная тема не очень освещена в интернете, да и документация далека от идеала.

Недавно у меня появилась задача: написать PHP приложение для поиска сообщений на Gmail ящике пользователя. Притом не просто поиск, а поиск по параметрам, благо Gmail имеет неплохую строку поиска, позволяющую написать что то вида "is:sent after:2012/08/10". Да и в API есть расширения IMAP протокола X-GM-*

Итак, нам требуется реализовать интерфейс для авторизации пользователей и поиска сообщений. Для данных целей я использовал Zend Framework, так как проект написан на Zend Framework, да и Google рекомендует его использовать для работы с API.

Обрисуем интерфейс:

class Model_OAuth_Gmail {
    
    // авторизуемся используя OAuth
    public function Connect( $callback );

    // получаем соединение используя Access Token ( выдан нам при подключении )
    public function getConnection($accessToken);
    
// типы ответа для метода поиска
    const MODE_NONE = 0;
    const MODE_MESSAGES = 1;
    const MODE_THREAD = 2;
// поиск сообщений: используя соединение( от getConnection ), параметры и тип ответа
    public function searchMessages($imapConnection, $params, $mode = 0);
}

Что делает каждый метод я написал в комментариях.
Примечание: да я знаю что такое синглтон и что этот класс стоит так реализовать, но суть не в этом!

Итак, начнем:

Connect


    public function Connect( $callback ) {
        $this -> urls['callbackUrl'] = $callback;
        $session = new Zend_Session_Namespace('OAuth');

        $OAuth_Consumer = new Zend_Oauth_Consumer(array_merge($this->config, $this->urls));

        try {
            if (!isset($session -> accessToken)) {
                if (!isset($session -> requestToken)) {
                    $session -> requestToken = $OAuth_Consumer -> getRequestToken(array('scope' => $this -> scopes), "GET");
                    $OAuth_Consumer -> redirect();
                } else {
                    $session -> accessToken = $OAuth_Consumer -> getAccessToken($_GET, $session -> requestToken);
                }
            }
            $accessToken = $session -> accessToken;

            $session -> unsetAll();
            unset($session);
            return $accessToken;
        } catch( exception $e) {
            $session -> unsetAll();
            throw new Zend_Exception("Error occurred. try to reload this page", 5);
        }
    }

Все довольно просто: Запускаем сессию, перекидываем на Google для нажатия кнопки Grant access и получаем Access Token, с помощью переданного нам Request Token"а

Главное не забыть сделать блок try-catch, т.к. если, к примеру, пользователь нажмёт назад, то больше, пока сессия не будет очищена, он авторизоваться не сможет (Request Token сохраняется на первом шаге)!

Ну и чуть не забыл конфиги:

    protected $config = array(
    'requestScheme' => Zend_Oauth::REQUEST_SCHEME_HEADER,
    'version' => '1.0',
    'consumerKey' => 'anonymous',
    'signatureMethod' => 'HMAC-SHA1',
    'consumerSecret' => 'anonymous',
    );

    protected $urls = array('callbackUrl' => "",
    'requestTokenUrl' => 'https://www.google.com/accounts/OAuthGetRequestToken',
    'userAuthorizationUrl' => 'https://www.google.com/accounts/OAuthAuthorizeToken',
    'accessTokenUrl' => 'https://www.google.com/accounts/OAuthGetAccessToken'
    );

    protected $scopes = 'https://mail.google.com/ https://www.googleapis.com/auth/userinfo#email';

getConnection


    public function getConnection($accessToken) {

        $config = new Zend_Oauth_Config();
        $config -> setOptions($this::config);
        $config -> setToken(unserialize($user::accessToken));
        $config -> setRequestMethod('GET');
        $url = 'https://mail.google.com/mail/b/' . $user -> email . '/imap/';
        $urlWithXoauth = $url . '?xoauth_requestor_id=' . urlencode($user -> email);

        $httpUtility = new Zend_Oauth_Http_Utility();

        /**
         * Get an unsorted array of oauth params,
         * including the signature based off those params.
         */
        $params = $httpUtility -> assembleParams($url, $config, array('xoauth_requestor_id' => $user -> email));

        /**
         * Sort parameters based on their names, as required
         * by OAuth.
         */
        ksort($params);

        /**
         * Construct a comma-deliminated,ordered,quoted list of
         * OAuth params as required by XOAUTH.
         *
         * Example: oauth_param1="foo",oauth_param2="bar"
         */
        $first = true;
        $oauthParams = '';
        foreach ($params as $key => $value) {
            // only include standard oauth params
            if (strpos($key, 'oauth_') === 0) {
                if (!$first) {
                    $oauthParams .= ',';
                }
                $oauthParams .= $key . '="' . urlencode($value) . '"';
                $first = false;
            }
        }

        /**
         * Generate SASL client request, using base64 encoded
         * OAuth params
         */
        $initClientRequest = 'GET ' . $urlWithXoauth . ' ' . $oauthParams;
        $initClientRequestEncoded = base64_encode($initClientRequest);

        /**
         * Make the IMAP connection and send the auth request
         */
        $imap = new Zend_Mail_Protocol_Imap('imap.gmail.com', '993', true);
        $authenticateParams = array('XOAUTH', $initClientRequestEncoded);
        $imap -> requestAndResponse('AUTHENTICATE', $authenticateParams);

        return $imap;
    }

Этот метод есть в примере использования у Google, он документирован и работает "как есть". К тому же он довольно простой.

Ну и переходим к самому интересному:

searchMessages


Вначале алгоритм действий:
  1. Выстраиваем на основе параметров строку поиска
  2. Находим ID сообщений удовлетворяющих условиям
  3. Преобразуем их в зависимости от $mode
  4. PROFIT! :)

Пункт 1:

        $searchString = 'X-GM-RAW "';

        foreach ($params as $key => $value)
            switch ($key) {
                // this is dates
                case "before" :
                case "after" :
                    $searchString .= $key . ":" . date("Y/m/d", $value) . " ";
                    break;
                
                // this is simple strings
                default :
                    $searchString .= $key . ":" . $value . " ";
                    break;
            }

        $searchString = trim($searchString) . '"';

Просто проходим по массиву с параметрами и преобразуем их в строку. Исключения составляют лишь даты, которые мы будем преобразовывать сами.

Пункт 2:

        $messages = $imapConnection -> search(array($searchString));

Просто, правда? Но как оказалось это решение не работает вообще. Сервер выдаст ошибку, т.к. мы не выполнили команду EXAMINE "INBOX". Ну ладно:

    if (isset($params['in'])){
        $imapConnection->examine(strtoupper(($params['in'])));
    } else {
        $imapConnection->examine("INBOX");
    }
    $messages = $imapConnection -> search(array($searchString));

Это решение уже работает, и почти правильно работает. Но, как только придется искать в исходящих(in:sent), мы получим неверный ответ. Я потратил много времени копаясь с этой проблемой, и ответ был найден.

Оказалось что у Gmail папки называются не SENT, INBOX, ..., а имеют названия зависящие от локали (оО). Пришлось сделать простой метод преобразования названий папок:

    protected function getFolder($imap, $folder) {
        $response = $imap -> requestAndResponse('XLIST "" "*"');
        $folders = array();
        foreach ($response AS $item) {
            if ($item[0] != "XLIST") {
                continue;
            }
            $folders[strtoupper(str_replace('\\', '', end($item[1])))] = $item[3];
        }
        return $folders[$folder];
    }

Просто узнаем список папок и найдем нужную. Но на этом, как оказалось, не все. EXAMINE от проблемы все равно не спасает, а вызывать нужно метод select для выбора папки перед поиском.

        if (isset($params['in']))
            $imapConnection -> select($this -> getFolder($imapConnection, strtoupper($params['in'])));
        
        $messages = $imapConnection -> search(array($searchString));

Теперь у нас есть ID найденых сообщений, дело за малым - преобразовать к виду сообщений.

        switch ( $mode ) {
            case $this::MODE_NONE :
                return $messages;

            case $this::MODE_MESSAGES :
                // fetching (get content of messages)
                $messages = $imapConnection -> requestAndResponse("FETCH " . implode(',', $messages) . " (X-GM-THRID)");
                return $messages;
            case $this::MODE_THREAD :
                $messages = $imapConnection -> requestAndResponse("FETCH " . implode(',', $messages) . " (X-GM-THRID)");
                $storage = new Zend_Mail_Storage_Imap($imapConnection);
                $storage -> selectFolder( $this -> getFolder($imapConnection, strtoupper($params['in'])) );
                $threads = array();
                if ($messages)
                    foreach ($messages AS $message) {
                        if (isset($message[2][1])) {
                            $thread_id = $message[2][1];
                            if (!isset($threads[$thread_id])) {
                                $threads[$thread_id] = array('all' => $imapConnection -> requestAndResponse("SEARCH X-GM-THRID $thread_id"), 'my' => array());
                                unset($threads[$thread_id]['all'][0][0]);
                            }

                            $threads[$thread_id]['my'][] = $message[0];
                        }
                    }

                $result = array();
                foreach ($threads as $thread)
                    if (!array_slice($thread['all'], array_search(max($thread['my']), $thread['all']) + 1))
                        $result[$storage -> getUniqueId(max($thread['my']))] = $storage -> getMessage(max($thread['my']));

                return array_reverse($result);
            // for right order
        }

В 1ом случае так и вернем массив идентификаторов, во втором получим сами сообщения, но самый интересный 3ий случай. 

Здесь мы используем Zend_Mail_Storage_Imap для получения сообщений в виде Zend_Mail_Message. 

Не стоит забывать что Zend_Mail_Storage_Imap ничего не знает о выбранной нам папке(у нас стала другая нумерация сообщений), по этому не забудем вызвать метод selectFolder.

Процесс преобразования простой: получим тред сообщения, преобразуем к виду: [все сообщения, мои сообщения]. Дальше выбираем последнее сообщение треда и формируем результат.

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

Вот и все! Спасибо всем за внимание. Надеюсь, что статья окажется вам полезной.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 17.08.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
NERO 2016 Classic ESD. Электронный ключ
Купить Антивирус Dr.Web Server Security Suite для сервера
SAP® Crystal Dashboard Design Departmental 2016 WIN INTL NUL
Rational ClearQuest Floating User License
Quest Software. Toad for SQL Server Development Suite
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
СУБД Oracle "с нуля"
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Реестр Windows. Секреты работы на компьютере
Новые материалы
Мир OLAP и Business Intelligence: новости, статьи, обзоры
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100