Загрузка фотографий на сайт 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 и немного переделаны (совсем немного).

В моем коде они выглядят так:

/* 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 и подсоединимся к серверу.

$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();
}

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

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

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

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 (например, каждый час).

Спасибо на внимание. Желаю удачи!

Записи