Pull to refresh

Загрузка фотографий на сайт c помощью электронной почты

Reading time7 min
Views579
Это мой первый пост на Хабре, по этому не судите строго.

Задача.


Реализовать возможность загрузки фотографий в профайл или в фотоленту события посредством электронной почты, поместить их в заданную папку и сделать соответственную запись в БД.

Алгоритм


Пользователь отправляет письмо с фотографиями на адрес типа userXXX_eventYYY@mysite.com, где eventYYY — ID события; userXXX — ID пользователя. Такого e-mail адреса НЕ СУЩЕСТВУЕТ. По этому все письма отправленные на несуществующие адреса перенаправляем на image_upload@mysite.com. Потом, при считывании почты с этого адреса, парсим заголовки и узнаем на какой адрес изначально было отправлено письмо. Распарсив полученный адрес, узнаем КУДА складывать файлы и кто их залил.

Использование PEAR


Для работы с POP3 сервером использовалась библиотека PEAR, в частности класс Net_POP3, который находится в файле path/to/pear/Net/POP3.php. Для его успешной работы необходим класс Net_Socket (path/to/pear/Net/Socket.php) и, собственно, PEAR.php. Все эти файлы находятся на сайте http://pear.php.net и доступны для скачивания.
Если PEAR у вас не установлена — просто скопируйте POP3.php и Socket.php в вашу папку с библиотеками и перепропишите пути в них. Файл PEAR.php есть в папке path/to/pear/.

Дополнительные функции


Для обработки прикрепленных файлов мне понадобились еще несколько функций. Не буду приписывать себе их авторство. Они взяты с сайта http://webi.ru/webi_articles/6_12_f.html и немного переделаны (совсем немного).
В моем коде они выглядят так:
/* START FUNCTIONS BLOCK */
// Функция для выдергивания метки boundary из заголовка Content-Type
function get_boundary($ctype){
 if(preg_match('/boundary[ ]?=[ ]?(["]?.*)/i',$ctype,$regs)) {
  $boundary = preg_replace('/^\"(.*)\"$/', "\\1", $regs[1]);
  return trim("--$boundary");
 }
}

// если письмо будет состоять из нескольких частей (текст, файлы и т.д.)
// то эта функция разобьет такое письмо на части (в массив), согласно разделителю boundary
function split_parts($boundary,$body) {
 $startpos = strpos($body,$boundary)+strlen($boundary)+2;
 $lenbody = strpos($body,"\r\n$boundary--") - $startpos;
 $body = substr($body,$startpos,$lenbody);
 return explode($boundary."\r\n",$body);
}

// Эта функция отделяет заголовки от тела и возвращает массив с заголовками и телом 
function fetch_structure($email) {
 $ARemail = Array();
 $separador = "\r\n\r\n";
 $header = trim(substr($email,0,strpos($email,$separador)));
 $bodypos = strlen($header)+strlen($separador);
 $body = substr($email,$bodypos,strlen($email)-$bodypos);
 $ARemail["header"] = $header;
 $ARemail["body"] = $body;
 return $ARemail;
}

// разбирает все заголовки и выводит массив, в котором каждый элемент является соответсвующим заголовком
function decode_header($header) {
 $headers = explode("\r\n",$header);
 $decodedheaders = Array();
 foreach($headers as $header_item){
  $thisheader = trim($header_item);
  if(!empty($thisheader))
  {
   if(!ereg("^[A-Z0-9a-z_-]+:",$thisheader))
    $decodedheaders[$lasthead] .= " $thisheader";
   else {
    $dbpoint = strpos($thisheader,":");
    $headname = strtolower(substr($thisheader,0,$dbpoint));
    $headvalue = trim(substr($thisheader,$dbpoint+1));
    if($decodedheaders[$headname] != "")
     $decodedheaders[$headname] .= "; $headvalue";
    else
     $decodedheaders[$headname] = $headvalue;
    $lasthead = $headname;
   }
  }
 }
 return $decodedheaders;
}

// перекодировщик тела письма.
// Само письмо может быть закодировано и данная функция приводит тело письма в нормальный вид.
// Так же и вложенные файлы будут перекодироваться этой функцией.
function compile_body($body,$enctype,$ctype) {
 $enctype = explode(" ",$enctype); $enctype = $enctype[0];
 if(strtolower($enctype) == "base64")
 $body = base64_decode($body);
 elseif(strtolower($enctype) == "quoted-printable")
 $body = quoted_printable_decode($body);
 if(ereg("koi8", $ctype)) $body = convert_cyr_string($body, "k", "w");
 return $body;
}
/* END FUNCTIONS BLOCK */


* This source code was highlighted with Source Code Highlighter.


Основной код (email_upload.php)


Теперь, когда все готово, пришло время писать скрипт для получения писем и их обработки.
Для начала, создадим объект $pop3 и подсоединимся к серверу.

$user='username';
$pass='secure';
$host='mysite.com';
$port="110";

// Создание объекта
$pop3 =& new Net_POP3();

// Соединение с сервером
if(PEAR::isError( $ret= $pop3->connect($host , $port ) )){
  echo "ERROR: " . $ret->getMessage() . "\n";
  exit();
}

// Авторизация на сервере
if(PEAR::isError( $ret= $pop3->login($user , $pass,'USER' ) )){
  echo "ERROR: " . $ret->getMessage() . "\n";
  exit();
}


* This source code was highlighted with Source Code Highlighter.


Теперь все готово для получения списка писем и их обработки. Это и делаем

$message_list=$pop3->getListing(); // Получаем массив писем.

* This source code was highlighted with Source Code Highlighter.


Пройдемся по всем письмам в цикле:

foreach($message_list as $message){
 $filenames[] = array();
 /* START GET PARSED HEADERS */
 // Получаем ИД текущено сообщения
 $message_id = $message['msg_id'];
 // Парсим заголовки
 $headers = $pop3->getParsedHeaders($message_id);
 
 $type = $ctype = $headers['Content-Type'];
 $ctype = split(";",$ctype);
 $types = split("/",$ctype[0]);
 $maintype = trim(strtolower($types[0]));
 $subtype = trim(strtolower($types[1]));
 
 /* END GET PARSED HEADERS */
 /* START CREATE FILE LOCATION AND SQL DATA*/
 
 // Получили данные с заголовка
 $from_user_info = $headers['Delivered-To'];
 
 // Далее получаем нужные АйДишники
 preg_match('/(user[0-9]+)_(event[0-9]+)@mysite.com/', $from_user_info, $matches);
 $user_id = str_replace("user","", $matches[1]);
 $event_id = str_replace("event","", $matches[2]);
 
 // Путь куда поместим файл
 $file_location = "/path/to/upload";
 
 // Помещаем данные для SQL запроса
 $table_data = array(
  'user_id' => $user_id,
  'event_id' => $event_id
 );
 
 /* END CREATE FILE LOCATION AND SQL DATA*/
 
 /* START GET BODY */ 
 // Получаем тело сообщения
 $message_text = htmlspecialchars($pop3->getBody($message_id));
 
 // Проверяем его тип (на содержание прикрепленных файлов)
 if($maintype=="multipart" && ereg($subtype,"signed,mixed,related")) // Если есть
 {
  // получаем метку-разделитель частей письма
  $boundary=get_boundary($headers['Content-Type']);
 
  // на основе этого разделителя разбиваем письмо на части
  $part = split_parts($boundary,$message_text);
  
  //Ищем файлы
  foreach($part as $part_item){
   // разбиваем текущую часть на тело и заголовки   
   $email = fetch_structure($part_item);
   $header = $email["header"];
   $body = $email["body"];
   
   // разбираем заголовки на массив
   $headers = decode_header($header);
   $ctype = $headers["Content-Type"];
   $cid = $headers["content-id"];
   $Actype = split(";",$ctype);
   $types = split("/",$Actype[0]);
   $rctype = strtolower($Actype[0]);
   
   // теперь проверяем, является ли эта часть прикрепленным файлом
   $is_download = (ereg("name=",$headers["content-disposition"].$headers["content-type"]) || $headers["X-Attachment-Id"] != "" || $rctype == "message/rfc822");
 
   if($is_download) {
    // Имя файла можно выдернуть из заголовков Content-Type или Content-Disposition
    $cdisp = $headers["content-disposition"];
    $ctype = $headers["content-type"];
    $ctype2 = explode(";",$ctype);
    $ctype2 = $ctype2[0];
    $Atype = split("/",$ctype);
    $Acdisp = split(";",$cdisp);
    $fname = $Acdisp[1];
    if(ereg("filename=\"(.*)\"",$fname,$regs))
     $filename = $regs[1];
    if($filename == "" && ereg("name=(.*)",$ctype,$regs))
     $filename = $regs[1];
    $filename = ereg_replace("\"(.*)\"","\\1",$filename);
    $filename = ereg_replace(""(.*)"","\\1",$filename);
  
    //читаем файл в переменную.
    $body = compile_body($body,$headers["content-transfer-encoding"],$ctype);
   
    // Указываем КУДА записать файл
    $filename = $file_location.trim($filename);
    
    // Формируем список файлов для записи в базу
    $filenames[] = $filename;    
    // Собственно сохраняем
    $ft=fopen($filename,"wb");
    fwrite($ft,$body);
    fclose($ft);
   }
  }
 }
 
 // НА основе $filenames[] и $table_data создаем запросы и выполняем их.
 $query = "INSERT INTO event_foto(event_id, user_id, image_name) VALUES ";
 $total_fotos = count($filenames);
 $current = 1;
 foreach($filenames as $file){
  $query .= "('$event_id','$user_id','$file')";
  if($total_fotos>$current)
   $query .= ", ";
  $current++;
 }
 mysql_query($query);
 //И не забываем удалить текущее письмо
 $pop3->deleteMsg($message_id);
}


* This source code was highlighted with Source Code Highlighter.


Конец


Собственно все. Модуль работает. Осталось только повесить этот файлик на CRON (например, каждый час).
Спасибо на внимание. Желаю удачи!
Tags:
Hubs:
+15
Comments26

Articles