(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
WinRAR 5.x Standard Licence - для частных лиц 1 лицензия
Zend Server with Z-Ray Developer Edition - Standard
ESET Secure Authentication newsale for 5 user, лицензия на 1 год
Stimulsoft Reports.Ultimate Single License Includes one year subscription
ESET NOD32 Smart Security - лицензия на 1 год на 3ПК или продление на 20 месяцев, Ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
СУБД Oracle "с нуля"
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Новые материалы
Все о PHP и даже больше
Краткие описания программ и ссылки на них
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
Обсуждения в форумах
Разработка программ базы данных (49)
Написание прикладных компьютерных программ (базы данных) на заказ. Разработка корпоративных...
 
работа на дому! (7)
Доброго времени суток дорогие друзья. Многоуровневый маркетинг окончательно признан...
 
Актуальные зеркала букмекерских контор (4)
Бетторы не всегда доверяют зеркалам БК, а зря, потому что эти «запасные аэродромы» запускают...
 
Ставки на спорт через зеркало БК Пинакл (1)
Актуальное зеркало букмекерской конторы Пинакл https://superbet.guru/pinacle-mirror/ всегда...
 
Ищу программиста для написания программы (57)
Ищу программиста ,владеющего Вижуал Бэйсик и программированием в Экселе, для написания...
 
 
 



    
rambler's top100 Rambler's Top100