|
|
Загрузка фотографий на сайт c помощью электронной почты | |
Задача
Реализовать возможность загрузки фотографий в профиль или в фотоленту события посредством электронной почты, поместить их в заданную папку и сделать соответственную запись в базу данных.
Алгоритм
Пользователь отправляет письмо с фотографиями на адрес типа [GUID]@mysite.com, где [GUID] генерируется уникально для каждой пары «пользователь+события» и он (пользователь) копирует / получает этот адрес для отправки после подписки на мероприятие или после регистрации нового профиля. Такого 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 и немного переделаны (совсем немного).
В моем коде они выглядят так:
| 1 | /* START FUNCTIONS BLOCK */
| | 2 | // Функция для выдергивания метки boundary из заголовка Content-Type
| | 3 | function get_boundary($ctype){
| | 4 | if(preg_match('/boundary[ ]?=[ ]?(["]?.*)/i',$ctype,$regs)) {
| | 5 | $boundary = preg_replace('/^\"(.*)\"$/', "\\1", $regs[1]);
| | 6 | return trim("--$boundary");
| | 7 | }
| | 8 | }
| | 9 |
| | 10 | // если письмо будет состоять из нескольких частей (текст, файлы и т.д.)
| | 11 | // то эта функция разобьет такое письмо на части (в массив), согласно разделителю boundary
| | 12 | function split_parts($boundary,$body) {
| | 13 | $startpos = strpos($body,$boundary)+strlen($boundary)+2;
| | 14 | $lenbody = strpos($body,"\r\n$boundary--") - $startpos;
| | 15 | $body = substr($body,$startpos,$lenbody);
| | 16 | return explode($boundary."\r\n",$body);
| | 17 | }
| | 18 |
| | 19 | // Эта функция отделяет заголовки от тела и возвращает массив с заголовками и телом
| | 20 | function fetch_structure($email) {
| | 21 | $ARemail = Array();
| | 22 | $separador = "\r\n\r\n";
| | 23 | $header = trim(substr($email,0,strpos($email,$separador)));
| | 24 | $bodypos = strlen($header)+strlen($separador);
| | 25 | $body = substr($email,$bodypos,strlen($email)-$bodypos);
| | 26 | $ARemail["header"] = $header;
| | 27 | $ARemail["body"] = $body;
| | 28 | return $ARemail;
| | 29 | }
| | 30 |
| | 31 | // разбирает все заголовки и выводит массив, в котором каждый элемент является соответсвующим заголовком
| | 32 | function decode_header($header) {
| | 33 | $headers = explode("\r\n",$header);
| | 34 | $decodedheaders = Array();
| | 35 | foreach($headers as $header_item){
| | 36 | $thisheader = trim($header_item);
| | 37 | if(!empty($thisheader))
| | 38 | {
| | 39 | if(!ereg("^[A-Z0-9a-z_-]+:",$thisheader))
| | 40 | $decodedheaders[$lasthead] .= " $thisheader";
| | 41 | else {
| | 42 | $dbpoint = strpos($thisheader,":");
| | 43 | $headname = strtolower(substr($thisheader,0,$dbpoint));
| | 44 | $headvalue = trim(substr($thisheader,$dbpoint+1));
| | 45 | if($decodedheaders[$headname] != "")
| | 46 | $decodedheaders[$headname] .= "; $headvalue";
| | 47 | else
| | 48 | $decodedheaders[$headname] = $headvalue;
| | 49 | $lasthead = $headname;
| | 50 | }
| | 51 | }
| | 52 | }
| | 53 | return $decodedheaders;
| | 54 | }
| | 55 |
| | 56 | // перекодировщик тела письма.
| | 57 | // Само письмо может быть закодировано и данная функция приводит тело письма в нормальный вид.
| | 58 | // Так же и вложенные файлы будут перекодироваться этой функцией.
| | 59 | function compile_body($body,$enctype,$ctype) {
| | 60 | $enctype = explode(" ",$enctype); $enctype = $enctype[0];
| | 61 | if(strtolower($enctype) == "base64")
| | 62 | $body = base64_decode($body);
| | 63 | elseif(strtolower($enctype) == "quoted-printable")
| | 64 | $body = quoted_printable_decode($body);
| | 65 | if(ereg("koi8", $ctype)) $body = convert_cyr_string($body, "k", "w");
| | 66 | return $body;
| | 67 | }
| | 68 | /* END FUNCTIONS BLOCK */ |
/* 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 */
Основной код (email_upload.php)
Теперь, когда все готово, пришло время писать скрипт для получения писем и их обработки.
Для начала, создадим объект $pop3 и подсоединимся к серверу.
| 1 | $user='username';
| | 2 | $pass='secure';
| | 3 | $host='mysite.com';
| | 4 | $port="110";
| | 5 |
| | 6 | // Создание объекта
| | 7 | $pop3 =& new Net_POP3();
| | 8 |
| | 9 | // Соединение с сервером
| | 10 | if(PEAR::isError( $ret= $pop3->connect($host , $port ) )){
| | 11 | echo "ERROR: " . $ret->getMessage() . "\n";
| | 12 | exit();
| | 13 | }
| | 14 |
| | 15 | // Авторизация на сервере
| | 16 | if(PEAR::isError( $ret= $pop3->login($user , $pass,'USER' ) )){
| | 17 | echo "ERROR: " . $ret->getMessage() . "\n";
| | 18 | exit();
| | 19 | } |
$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();
}
Теперь все готово для получения списка писем и их обработки. Это и делаем
| 1 | $message_list=$pop3->getListing(); // Получаем массив писем. |
$message_list=$pop3->getListing(); // Получаем массив писем.
Пройдемся по всем письмам в цикле:
| 1 | foreach($message_list as $message){
| | 2 | $filenames[] = array();
| | 3 | /* START GET PARSED HEADERS */
| | 4 | // Получаем ИД текущено сообщения
| | 5 | $message_id = $message['msg_id'];
| | 6 | // Парсим заголовки
| | 7 | $headers = $pop3->getParsedHeaders($message_id);
| | 8 |
| | 9 | $type = $ctype = $headers['Content-Type'];
| | 10 | $ctype = split(";",$ctype);
| | 11 | $types = split("/",$ctype[0]);
| | 12 | $maintype = trim(strtolower($types[0]));
| | 13 | $subtype = trim(strtolower($types[1]));
| | 14 |
| | 15 | /* END GET PARSED HEADERS */
| | 16 | /* START CREATE FILE LOCATION AND SQL DATA */
| | 17 |
| | 18 | // Получили данные с заголовка
| | 19 | $from_user_info = $headers['Delivered-To'];
| | 20 |
| | 21 | // Далее получаем нужные АйДишники
| | 22 | preg_match('/([a-zA-Z0-9]+)@mysite.com/', $from_user_info, $matches);
| | 23 | $user_guid = $matches[1];
| | 24 |
| | 25 | // На основании $user_guid получаем $user_id и $event_id из базы данных
| | 26 | // Путь куда поместим файл
| | 27 | $file_location = "/path/to/upload";
| | 28 |
| | 29 | // Помещаем данные для SQL запроса
| | 30 | $table_data = array(
| | 31 | 'user_id' => $user_id,
| | 32 | 'event_id' => $event_id
| | 33 | );
| | 34 |
| | 35 | /* END CREATE FILE LOCATION AND SQL DATA */
| | 36 |
| | 37 | /* START GET BODY */
| | 38 | // Получаем тело сообщения
| | 39 | $message_text = htmlspecialchars($pop3->getBody($message_id));
| | 40 |
| | 41 | // Проверяем его тип (на содержание прикрепленных файлов)
| | 42 | if($maintype=="multipart" && ereg($subtype,"signed,mixed,related")) // Если есть
| | 43 | {
| | 44 | // получаем метку-разделитель частей письма
| | 45 | $boundary=get_boundary($headers['Content-Type']);
| | 46 |
| | 47 | // на основе этого разделителя разбиваем письмо на части
| | 48 | $part = split_parts($boundary,$message_text);
| | 49 |
| | 50 | //Ищем файлы
| | 51 | foreach($part as $part_item){
| | 52 | // разбиваем текущую часть на тело и заголовки
| | 53 | $email = fetch_structure($part_item);
| | 54 | $header = $email["header"];
| | 55 | $body = $email["body"];
| | 56 |
| | 57 | // разбираем заголовки на массив
| | 58 | $headers = decode_header($header);
| | 59 | $ctype = $headers["Content-Type"];
| | 60 | $cid = $headers["content-id"];
| | 61 | $Actype = split(";",$ctype);
| | 62 | $types = split("/",$Actype[0]);
| | 63 | $rctype = strtolower($Actype[0]);
| | 64 |
| | 65 | // теперь проверяем, является ли эта часть прикрепленным файлом
| | 66 | $is_download = (ereg("name=",$headers["content-disposition"].$headers["content-type"]) || $headers["X-Attachment-Id"] != "" || $rctype == "message/rfc822");
| | 67 |
| | 68 | if($is_download) {
| | 69 | // Имя файла можно выдернуть из заголовков Content-Type или Content-Disposition
| | 70 | $cdisp = $headers["content-disposition"];
| | 71 | $ctype = $headers["content-type"];
| | 72 | $ctype2 = explode(";",$ctype);
| | 73 | $ctype2 = $ctype2[0];
| | 74 | $Atype = split("/",$ctype);
| | 75 | $Acdisp = split(";",$cdisp);
| | 76 | $fname = $Acdisp[1];
| | 77 | if(ereg("filename=\"(.*)\"",$fname,$regs))
| | 78 | $filename = $regs[1];
| | 79 | if($filename == "" && ereg("name=(.*)",$ctype,$regs))
| | 80 | $filename = $regs[1];
| | 81 | $filename = ereg_replace("\"(.*)\"","\\1",$filename);
| | 82 | $filename = ereg_replace(""(.*)"","\\1",$filename);
| | 83 |
| | 84 | //читаем файл в переменную.
| | 85 | $body = compile_body($body,$headers["content-transfer-encoding"],$ctype);
| | 86 |
| | 87 | // Указываем КУДА записать файл
| | 88 | $filename = $file_location.trim($filename);
| | 89 |
| | 90 | // Формируем список файлов для записи в базу данных
| | 91 | $filenames[] = $filename;
| | 92 | // Собственно сохраняем
| | 93 | $ft=fopen($filename,"wb");
| | 94 | fwrite($ft,$body);
| | 95 | fclose($ft);
| | 96 | }
| | 97 | }
| | 98 | }
| | 99 |
| | 100 | // НА основе $filenames[] и $table_data создаем запросы и выполняем их.
| | 101 | $query = "INSERT INTO event_foto(event_id, user_id, image_name) VALUES ";
| | 102 | $total_fotos = count($filenames);
| | 103 | $current = 1;
| | 104 | foreach($filenames as $file){
| | 105 | $query .= "('$event_id','$user_id','$file')";
| | 106 | if($total_fotos>$current)
| | 107 | $query .= ", ";
| | 108 | $current++;
| | 109 | }
| | 110 | mysql_query($query);
| | 111 | //И не забываем удалить текущее письмо
| | 112 | $pop3->deleteMsg($message_id);
| | 113 | } |
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('/([a-zA-Z0-9]+)@mysite.com/', $from_user_info, $matches);
$user_guid = $matches[1];
// На основании $user_guid получаем $user_id и $event_id из базы данных
// Путь куда поместим файл
$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);
}
Конец
Собственно все. Модуль работает. Осталось только повесить этот файлик на CRON (например, каждый час).
Спасибо на внимание. Желаю удачи!
|
|
|
|
|
|
 Новости inPHP.org
30.12.2011 С наступающим Новым Годом!
Команда inPHP.org поздравляет всех посетителей сайта проекта с наступающим Новым Годом! Вероятно, многие участники проекта заметили некоторое затишье в уходящем году. Поверьте, в следующем всё будет несколько иначе!
31.12.2010 С Новым Годом!
Уважаемые коллеги, коллектив проекта inphp.org искренне поздравляет вас с наступающим Новым Годом!
27.05.2010 Технические работы.
По техническим причинам возможны кратковременные перебои в работе сайта проекта в период с 27.05.2010 по 1.06.2010.
02.03.2010 Доступ участников к разработке тестирований.
В тестовом режиме запущен функционал, позволяющий участникам проекта разрабатывать
собственные тестирования. Разработанные сообществом тестирования не имеют ограничений официальной
аккредитации и в любое время доступны любому участнику.
|
 |
|
|
|
|
|
 Содействуют развитию
 Участники проекта
|
 |
|
|
|
|
|
 Статистика проекта
| Всего пользователей: | 3648 |
| Из них аккредитовано: | 1468 |
| Были сегодня: | 2 |
| Уровень | Пользователи | |
| 7 | 74 | 2% |
| 6 | 237 | 6,5% |
| 5 | 207 | 5,7% |
| 4 | 194 | 5,3% |
| 3 | 365 | 10% |
| 2 | 40 | 1,1% |
| 1 | 350 | 9,6% |
| отсутствует | 2180 | 59,8% |
|
 |
|
|
|
|