Календарь на Май 2024 года: calendar2008.ru/2024/may/
Навигация
Главная »  Новости 

Работа с 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. Процесс преобразования простой: получим тред сообщения, преобразуем к виду: [все сообщения, мои сообщения]. Дальше выбираем последнее сообщение треда и формируем результат. Также не забудем что результат нужно перевернуть, т.к. нумерация на сервере идет от старых к новым, ну а мы привыкли наоборот. Вот и все! Спасибо всем за внимание. Надеюсь, что статья окажется вам полезной.



 

 Минкомсвязи отрегулирует облака. Возможно введение уголовной ответственности.
 Big Data делает анонимность математически невозможной.
 Hewlett-Packard обновляет MSA.
 Создание окна мастера (исходники).
 Партнерство с компьютером. Человеко-машинные целеустремленные системы..


Главная »  Новости 

© 2024 Team.Furia.Ru.
Частичное копирование материалов разрешено.