Category Archive: PHP

Пишем свой сборщик писем на PHP (3 часть из 3)

Описание использованных функций (func.php):

<?php
 
//установим часовой пояс по умолчанию,
//чтобы вызов функции date() не вызывал предупреждений
date_default_timezone_set('Asia/Almaty');
 
//директория для хранения вложений писем
$FILES = "files";
//если директория не существует, то создаем её
if(!file_exists($FILES)) mkdir($FILES);
 
//директория для хранения логов
$LOG_DIR = "logs";
if(!file_exists($LOG_DIR)) mkdir($LOG_DIR);
 
//логи за один день будем хранить в одном файле.
//Например: logs/2014-07-31.log
$LOG_FILE = $LOG_DIR . DIRECTORY_SEPARATOR . date('Y-m-d') . ".log";
 
//функция для ведения логов
//логи пишутся в файл log.txt, который будет находится
//в той же директории что и скрипт, пишущий лог
function wr($msg){
    global $LOG_FILE;
	$date = date('d/m/Y H:i:s');
	file_put_contents($LOG_FILE, "$date $msg\n", FILE_APPEND);
}
 
//вывод на сообщения на экран
function pr($msg){
	echo "$msg<br>";
}
 
//вывод содержимого объекта на экран
function debugObj($obj){
	echo "<";
    echo "pre style='color:green'>";
	print_r($obj);
	echo "</";
    echo "pre>";
}
 
//получение заголовка письма в виде объекта
function getHeader($mbox,$uid){
    return imap_rfc822_parse_headers(getHeaderRaw($mbox,$uid));
}
 
//получение заголовка письма
function getHeaderRaw($mbox,$uid){
    return imap_fetchbody($mbox, $uid, '0', FT_UID);
}
 
//раскодировка заголовка
function getDecodedHeader($text){
    //imap_mime_header_decode - декодирует элементы MIME-шапки в виде массива
    //У каждого элемента указана кодировка(charset) и сам текст(text)
    $elements = imap_mime_header_decode($text);
    $ret = "";
    //перебираем элементы
    for ($i=0; $i<count($elements); $i++) {
        $charset = $elements[$i]->charset;//кодировка
        $text = $elements[$i]->text;//закодированный текст
        if($charset == 'default'){
            //если элемент не кодирован, то значение кодировки default
            $ret .= $text;
        }else{
            //приводим всё кодировке к UTF-8
            $ret .= iconv($charset,"UTF-8",$text);
        }
    }
    return $ret;
}
 
//получение содержимого письма в виде простого текста
function getTextBody($imap,$uid){
    return getPart($imap, $uid, "TEXT/PLAIN");
}
 
//получение содержимого письма в виде формате html
function getHtmlBody($imap,$uid){
    return getPart($imap, $uid, "TEXT/HTML");
}
 
//получение части письма
function getPart($imap, $uid, $mimetype) {
    //получение структуры письма
    $structure = imap_fetchstructure($imap, $uid, FT_UID);
    if ($structure) {
        if ($mimetype == getMimeType($structure)) {
            $partNumber = 1;
            //imap_fetchbody - извлекает определённый раздел тела сообщения
            $text = imap_fetchbody($imap, $uid, $partNumber, FT_UID);
 
            $charset = $structure->parameters[0]->value;//кодировка символов
 
            //0 - 7BIT; 1 - 8BIT; 2 - BINARY; 3 - BASE64; 4 - QUOTED-PRINTABLE; 5 - OTHER
            switch ($structure->encoding) {
                case 3:
                    //imap_base64 - декодирует BASE64-кодированный текст
                    $text = imap_base64($text);
                    break;
                case 4:
                    //imap_qprint - конвертирует закавыченную строку в 8-битную строку
                    $text = imap_qprint($text);
                    break;
            }
 
            if($mimetype == 'TEXT/PLAIN'){
                $text = iconv($charset,"UTF-8",$text);
            }
 
            if($mimetype == 'TEXT/HTML'){
                $text = iconv($charset,"UTF-8",$text);
            }
 
            return $text;
        }
    }
    return false;
}
 
//MIME-тип передается числом, а подтип - текстом.
//Функция приводит все в текстовый вид.
//Например: если type = 0 и subtype = "PLAIN",
//то функция вернет "TEXT/PLAIN".
//TEXT - 0, MULTIPART - 1, .. , APPLICATION - 3 и т.д.
function getMimeType($structure) {
    $primaryMimetype = array("TEXT", "MULTIPART", "MESSAGE", "APPLICATION", 
        "AUDIO", "IMAGE", "VIDEO", "OTHER");
    if ($structure->subtype) {
        return $primaryMimetype[(int)$structure->type] . "/" . $structure->subtype;
    }
    return "TEXT/PLAIN";
}
 
//перевести текст в дату MySQL
function strToMysqlDate($text){
    $unixTimestamp=strtotime($text);
    return date("Y-m-d H:i:s", $unixTimestamp);
}
 
//заполняем ассоциативный массив, где ключом является тип адреса,
//а значение массив адресов
function getAddress($header,$type,&$map){
    //проверка существования типа в заголовке
    if(property_exists($header,$type)){
        $arr = $header->$type;
        if(is_array($arr) && count($arr) > 0){
            $map[$type] = $arr;
        }
    }
}
 
//загрузка вложений
function loadAttaches($mbox,$uid,$message_id){
    //получаем структуру сообщения
    $struct = imap_fetchstructure($mbox,$uid,FT_UID);
    $attachCount = 0;
    if(!$struct->parts) return $attachCount;
    //перебираем части сообщения
    foreach($struct->parts as $number => $part){
        //ищем части, у которых ifdisposition равно 1 и disposition равно ATTACHMENT,
        //все остальные части игнорируем. Также стоит заметить, что значение поля
        //disposition может быть как в верхнем, так и в нижнем регистрах,
        //т.е. может быть "attachment" и "ATTACHMENT". Поэтому в коде всё приведено
        //к верхнему регистру
        if(!$part->ifdisposition 
            || strtoupper($part->disposition) != "ATTACHMENT")continue;
        //получаем название файла
        $filename = getDecodedHeader($part->dparameters[0]->value);
        //получаем содержимое файла в закодированном виде
        $text = imap_fetchbody($mbox, $uid, $number + 1, FT_UID);
        //декодирование содержимого файла
        switch ($part->encoding) {
            case 3:
                $text = imap_base64($text);
                break;
            case 4:
                $text = imap_qprint($text);
                break;
        }
        //оригинальное название файла будем сохранять в базе данных.
        //Разные письма могут иметь вложения с одинаковыми названиями,
        //поэтому в файловой системе будем сохранять файла с уникальным именем,
        //сохранив при этом расширение файла
        $file_path = getStoreDirectory() . getUid() . getFileExtension($filename);
        file_put_contents($file_path,$text);
 
        $content_type = getMimeType($part);//MIME-тип файла
        $filesize = strlen($text);//размер файла
 
        //записываем информацию о файле в базу данных. Напомню, что в
        //базу сохраняется не сам файл, а относительный путь к файлу
        $sql = "INSERT INTO attachments(message_id,file_name,mime_type,
            file_size,location)" .
            "VALUES('$message_id',
            '" . mysql_real_escape_string($filename) . "',
            '" . mysql_real_escape_string($content_type) . "',
            $filesize,
            '" . mysql_real_escape_string($file_path) . "')";
        $res_ins = mysql_query($sql) or die(mysql_error());
        $attachCount++;
    }
    return $attachCount;
}
 
//Функция для получения пути к директории, где будут храниться файлы.
//Файлы будут сохраняться в поддиректории, созданной по
//текущей дате. Например, 2014-07-31. Это позволит
//не держать файлы в одной директории. Много файлом в
//одной директории замедляет чтение директории
function getStoreDirectory(){
    global $FILES;
    $date_folder = "$FILES/" . date('Y-m-d') . "/";
    if(!file_exists($date_folder)) mkdir($date_folder);
    return $date_folder;
}
 
//генерация уникального идентификатора
function getUid(){
    if (function_exists('com_create_guid')){
        return str_replace("}", "", str_replace("{", "", com_create_guid()));
    } else {
        mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up.
        $charid = strtoupper(md5(uniqid(rand(), true)));
        $hyphen = chr(45);// "-"
        $uuid =
            substr($charid, 0, 8).$hyphen
            .substr($charid, 8, 4).$hyphen
            .substr($charid,12, 4).$hyphen
            .substr($charid,16, 4).$hyphen
            .substr($charid,20,12);
 
    }
    return strtolower($uuid);
}
 
//получаем расширение файла
function getFileExtension($filename){
    $arr = explode(".",$filename);
    return count($arr) > 1 ? "." . end($arr) : "";
}
?>

Файл для подключения к базе данных (connect.php):

<?php
//настройки подключания базе данных MySQL
$con_host = "localhost";
$con_port = "3306";
$con_user = "root";
$con_pass = "YOUR_PASSWORD";
$sel_base = "mail_collector";
mysql_connect($con_host.":".$con_port,$con_user,$con_pass)
    or die("Error of connection to MySQL");
mysql_select_db($sel_base) or dir("Error of selecting database");//выбор базы данных
mysql_query("SET NAMES 'utf8'");//установка кодировки UTF-8
?>

Исходный код можно скачать здесь

Пишем свой сборщик писем на PHP (2 часть из 3)

Скрипт для скачивания писем (loader.php)

<?php
require_once "func.php";//подключаем функции
require_once "connect.php";//подключаем настройки к MySQL
 
wr("\n=============================\n");
 
//некоторые строки уже прокомменитированы в коде (см. reader.php),
//поэтому оставим их без комментариев
set_time_limit(0);
$lock = "loader.lock";
$aborted = file_exists($lock) ? filemtime($lock) : false;
$fp = fopen($lock,'w');
register_shutdown_function(function() use ($fp, $lock) {
    wr("shutdown");
    flock($fp, LOCK_UN);//снимаем блокировку с файла
    fclose($fp);//закрываем файл
    unlink( __DIR__ . DIRECTORY_SEPARATOR . $lock);//удаляем файл
});
if(!flock($fp,LOCK_EX|LOCK_NB)){
    wr("busy\n");//пишем в лог, что занято
}else{
    if($aborted){
        wr("Aborted\n");
    }
 
    //составим список только тех почтовых ящиков,
    //сообщения которых еще не скачаны
    $sql = "SELECT b.*
            FROM mailboxes b
            JOIN(
                SELECT mailbox_id FROM messages GROUP BY mailbox_id
            )m ON m.mailbox_id = b.id";
    $res = mysql_query($sql);
    $mailboxes = array();
    while($row = mysql_fetch_array($res)){
        array_push($mailboxes,$row);
    }
 
    //перебор почтовых ящиков
    foreach($mailboxes as $mailbox){
        $mailbox_id = $mailbox['id'];
        $host = $mailbox['host'];//адрес почтового сервера
        $port = $mailbox['port'];//порт почтового сервера
        $user = $mailbox['email'];//имя пользователя (почтовый ящик)
        $password = $mailbox['password'];//пароль к почтовому ящику
        $ssl = $mailbox['is_ssl'] ? "/ssl" : "";
        //строка подключения
        $conn = "{{$host}:{$port}{$ssl}}";
        wr("Read $user, conn = $conn");
 
        //открываем IMAP-поток
        $mail = imap_open($conn,$user,$password);
        if(!$mail){
            //пишем в лог сообщение о неудачной попытке подключения
            wr("Error opening IMAP. " . imap_last_error());
            continue;//переходим к следующему ящику
        }
 
        mysql_query("SET AUTOCOMMIT=0");
        mysql_query("START TRANSACTION");
 
        //получаем список сообщений, которые необходимо скачать с почтового ящика
        $sql = "SELECT *
                FROM messages
                WHERE mailbox_id = $mailbox_id AND is_ready = false";
        $res = mysql_query($sql);
        while($message = mysql_fetch_array($res)){
            $message_id = $message['id'];//ID письма в базе данных
            $message_uid = $message['uid'];//уникальный номер письма
            $headerRaw = getHeaderRaw($mail,$message_uid);//технический заголовок письма
            $header = getHeader($mail,$message_uid);//заголовок письма
            $subject = getDecodedHeader($header->subject);//тема письма
            $headerDate = strToMysqlDate($header->date);//дата письма
            $body_text = getTextBody($mail,$message_uid);//содержимое письма в виде простого текста
            $body_html = getHtmlBody($mail,$message_uid);//содержимое письма в формате html
 
            //получение адресов из заголовка письма
            $address_map = array();
            $address_types = array('to','from','reply_to','sender','cc','bcc');
            foreach($address_types as $address_type){
                getAddress($header,$address_type,$address_map);
            }
 
            foreach($address_map as $key => $arr){
                foreach($arr as $obj){
                    $type = $key;
                    $address = "$obj->mailbox@$obj->host";//склеиваем email
                    $sql = "INSERT INTO addresses(message_id,type,email)
                            VALUES($message_id,
                            '" . mysql_real_escape_string($type) . "',
                            '" . mysql_real_escape_string($address) . "')";
                    mysql_query($sql) or wr(mysql_error());
                }
            }
 
            //считываем вложения и получаем кол-во вложений,
            //которое записываем в базу данных
            $attachCount = loadAttaches($mail,$message_uid,$message_id);
 
            $sql = "UPDATE messages
                    SET subject = '" . mysql_real_escape_string($subject) . "',
                        body_text = '" . mysql_real_escape_string($body_text) . "',
                        body_html = '" . mysql_real_escape_string($body_html) . "',
                        header = '" . mysql_real_escape_string($headerRaw) . "',
                        message_date = '" . mysql_real_escape_string($headerDate) . "',
                        attachment_count = $attachCount,
                        modify_date = now(),
                        is_ready = true
                    WHERE id = $message_id";
            mysql_query($sql) or wr(mysql_error());
            mysql_query("COMMIT");
        }
 
    }
 
}
?>

Пишем свой сборщик писем на PHP (1 часть из 3)

В данной статье будет описано создание сборщика писем на PHP. Если в один прекрасный день у вас возникла необходимость забирать письма с почтового (почтовых) ящиков и куда-то складывать (скорее всего, в базу данных), то добро пожаловать. Очень надеюсь, что статья вам пригодиться и облегчит жизнь. В третьей части статьи будут приложены все необходимые коды. И так поехали…

Сначала определимся с требованиями:
1) Реализация на PHP
2) Чтение писем по протоколу IMAP
3) Запись содержимого писем в базу MySQL
4) Сохранение вложений к письмам в файловой системе
5) Будет реализовано два скрипта: первый проверяет новые письма, а второй закачивает письма
6) Будет таблица, содержащая список почтовых ящик, с которых собираются письма
7) Также будет сохраняться информация о том: от кого письмо, кому письмо, кому переадресовано, кто в копии и кто в скрытой копии

Почему именно такой выбор?
Письма можно читать с почтового сервера по протоколам IMAP и POP3.
Реализация сборщика будет построена с использованием протокола IMAP, т.к. он имеет ряд преимуществ. Не знаю как в реализациях POP3 на других языках, но в PHP мне не удалось получить UID письма, можно получить только порядковый номер письма среди новых. Также минусом при использовании POP3 является то, что письма нужно считывать с первого раза. А у нас будет другая реализация: письма сперва проверяются, а только потом закачиваются, что возможно благодаря IMAP.
Вложения к письмам будем хранить в файловой системе, а в базе данных будут храниться только пути к этим файлам (хотя вам никто не запрещает записывать файлы в базу данных)

ПРОЕКТИРОВАНИЕ ТАБЛИЦ
Таблица почтовых ящиков
Создадим в MySQL новую базу данных mail_collector с кодировкой UTF-8 по умолчанию:

CREATE DATABASE mail_collector DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;

Создадим таблицу mailboxes, которая будет хранить необходимую информацию для подключения к почтовому ящику.

CREATE TABLE mailboxes
(
	id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
	email varchar(50) NOT NULL,
	password varchar(50) NOT NULL, 
	host varchar(50) NOT NULL, 
	port varchar(50) NOT NULL, 	
	is_ssl bit NOT NULL,   
	is_deleted bit NOT NULL,
	last_message_uid int NOT NULL	
);

Назначение полей:
email — почтовый ящик
password — пароль от ящика
host — адрес почтового сервера. Например, imap.gmail.com или 173.194.71.108
port — порт, по которому работает почтовый сервер
is_ssl — если флаг установлен, то подключение будет по SSL
is_deleted — если флаг установлен, то почтовый ящик считается удаленным и не участвует в сборке писем
last_message_uid — поле используется для хранения UID последнего считанного сообщения

Заполним таблицу mailboxes данными трех почтовых ящиков, созданных на разных почтовых серверах: gmail, yandex и mail.ru. В качестве примера к почтовым ящикам на gmail и yandex будем подключаться через SSL, а на mail.ru — без SSL.

INSERT INTO mailboxes(email,password,host,port,is_ssl,is_deleted) VALUES
('mail.collector.kz@gmail.com','mail.collector','imap.gmail.com','993',1,0,0),
('mail.collector.kz@mail.ru','mail.collector','imap.mail.ru','143',0,0,0),
('mail.collector.kz@yandex.ru','mail.collector','imap.yandex.ru','993',1,0,0)

Таблица сообщений
Создадим таблицу messages для хранения сообщений:

CREATE TABLE messages
(
	id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
	mailbox_id int NOT NULL,
	uid int NOT NULL,
	subject varchar(255),
	body_text text,
	body_html text,
	attachment_count int,
	header text,
	message_date datetime,
	create_date datetime NOT NULL,
	modify_date datetime,	
	is_ready bit NOT NULL 	
);

Так же создадим внешний ключ на таблицу mailboxes:

ALTER TABLE messages ADD CONSTRAINT fk_messages_user_id 
FOREIGN KEY (mailbox_id) REFERENCES mailboxes(id);

Назначение полей:
mailbox_id — ссылка на почтовый ящик, к которому относится письмо
uid — уникальный номер письма в почтовом ящике
subject — тема письма
body_text и body_html — письмо храниться на сервере в виде обычного текста и html-версии
attachment_count — кол-во вложений письма
header — технический заголовок письма
message_date — дата письма
create_date — дата создания записи
modify_date — дата изменения записи
is_ready — письмо полностью загружено

Таблица адресов
Создадим таблицу addresses для хранения адресов: адрес отправителя, адреса получателей, адреса получателей скрытой копии и т.д.

CREATE TABLE addresses
(
	id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
	message_id int NOT NULL,
	type varchar(10) NOT NULL,
	email varchar(50) NOT NULL
);

И создадим внешний ключ для связи с таблицей messages:

ALTER TABLE addresses ADD CONSTRAINT fk_addresses_message_id 
FOREIGN KEY (message_id) REFERENCES messages(id);

Назначение полей:
message_id — ссылка на сообщение, к которому относится данный адрес
type — тип адресата: «from» (отправитель), «to»(получатель), «cc» (скрытая копия) и т.д.
email — адрес электронной почты
Таблица вложений
Создадим таблицу attachments для хранения информации о файлах прикрепленных к письму:

CREATE TABLE attachments
(
	id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
	message_id int NOT NULL,
	file_name varchar(255) NOT NULL,
	mime_type varchar(255) NOT NULL,
	file_size int NOT NULL,
	location varchar(255) NOT NULL	
);

Создадим внешний ключ для связи с таблицей messages:

ALTER TABLE attachments ADD CONSTRAINT fk_attachments_message_id 
FOREIGN KEY (message_id) REFERENCES messages(id);

Назначение полей:
message_id — привязка к конкретному сообщению
file_name — имя файла
mime_type — тип данных: «application/zip», «application/pdf», «image/png» и т.д.
file_size — размер файла в байтах
location — расположение файла

СКРИПТЫ
Описание работы: первый скрипт подключается к почтовому серверу, записывает в базу данных UID-ы новых писем, а второй скрипт, который будет рассмотрен позднее, по UID писем закачивает содержимое писем с вложениями и другой информацией.
Скрипт для чтения новых писем (reader.php)

<?php
require_once "func.php";//подключаем функции
require_once "connect.php";//подключаем настройки к MySQL
 
wr("\n=============================\n");
 
//отключаем ограничение по времени испольнения
//по умолчанию тайм-аут 60 секунд
set_time_limit(0);
 
//предполагается, что данный скрипт будет запускаться планировщиком
//через равные промежутки времени. Например, каждые 30 секунд.
//Но может возникнуть ситуация, что предыдущий может не успеть завершить
//свою работу. Для решения этой проблемы, когда скрипт запускается он
//накладывает блокировку на заданный нами файл, совершив свою работу
//скрипт снимает блокировку с файла и удаляет его. В это время, если по
//расписанию сработал еще один запуск скрипта, то он увидит блокировку и
//завершиться ничего не делая
 
//reader.lock - файл, который мы будем использовать для блокировки.
//Файл блокировки будем храниться рядом со скриптом.
//Можно было бы хранить во временной директории, но на хостинге
//может быть ограничен доступ к временной директории
$lock = "reader.lock";
 
//логическая переменная $aborted будет сигнализировать о том, что
//предыдущий скрипт был прерван. Для этого проверяем существование
//файла, а затем пытается получить время последней модификации файла
//Если файл блокировки существует и удалось получить время последнего
//изменения файла, то значит предыдущий скрипт был прерван	
$aborted = file_exists($lock) ? filemtime($lock) : false;
 
$fp = fopen($lock,'w');//открывает файл с возможностью записи
 
//регистрируем функцию, которая выполниться по завершению работы скрипта
register_shutdown_function(function() use ($fp, $lock) {
	wr("shutdown");
	flock($fp, LOCK_UN);//снимаем блокировку с файла
	fclose($fp);//закрываем файл
	unlink( __DIR__ . DIRECTORY_SEPARATOR . $lock);//удаляем файл
});
 
//пытаемся установить на файл блокировку на запись (LOCK_EX),
//так же передаем флаг LOCK_NB, чтобы скрипт не ждал освобождения
//блокировки
if(!flock($fp,LOCK_EX|LOCK_NB)){
	//если не удалось наложить блокировку, то значит предыдущий 
	//скрипт еще работает.
	wr("busy\n");//пишем в лог, что занято
}else{
 
	if($aborted){
		//если выполнение предыдущего скрипта было прервано,
		//то в нашем случае просто пишем в лог, что прервано. 
		wr("Aborted\n");
	}
 
	//отключаем Autocommit, будем сами управлять транзакциями
	mysql_query("SET AUTOCOMMIT=0");
	mysql_query("START TRANSACTION");//стартуем транзакцию
 
	//запрос из базы списка действующих почтовых ящиков
	$sql = "SELECT * FROM mailboxes WHERE is_deleted = false";
	$res = mysql_query($sql);
 
	//перебираем почтовые ящики
	while($row = mysql_fetch_array($res)){
		$mailbox_id = $row['id'];
		$host = $row['host'];//адрес почтового сервера
		$port = $row['port'];//порт почтового сервера
		$user = $row['email'];//имя пользователя (почтовый ящик)
		$password = $row['password'];//пароль к почтовому ящику
		$last_uid = $row['last_message_uid'];//uid последнего считанного сообщения
		//если подключение идет через SSL, 
		//то достаточно добавить "/ssl" к строке подключения, и
		//поддержка SSL будет включена
		$ssl = $row['is_ssl'] ? "/ssl" : "";
 
		//строка подключения
		$conn = "{{$host}:{$port}{$ssl}}";
		wr("Read $user, conn = $conn");
 
		//открываем IMAP-поток
		$mail = imap_open($conn,$user,$password);
		if(!$mail){		
			//пишем влог сообщение о неудачной попытке подключения
			wr("Error opening IMAP. " . imap_last_error());
			continue;//переходим к следующему ящику
		}
 
		//Считываем только письма, у которых UID больше, 
		//чем UID последнего считанного сообщения. Для этого воспользуемся 
		//функцией imap_fetch_overview, у которой первый параметр - IMAP поток,
		//второй параметр - диапазон номеров и третий параметр - константа FT_UID.
		//FT_UID говорит о том, что диапазоны задаются UID-ами, иначе порядковыми
		//номерами сообщений. Здесь важно понять разницу. 
		//Порядковый номер письма показывает номер писема среди писем почтового ящика,
		//но если кол-во писем уменьшить, то порядковый номер может измениться.
		//UID письма - это уникальный номер письма, также присваивается по попрядку, 
		//но не изменяется.
 
		//Сейчас и в дальнейшем мы же будем полагаться только на UID писем.
		//Диапазаны можно задать следующим образом:
		//"2,4:6" - что соответствует UID-ам 2,4,5,6
		//"7:10" - соответствует 7,8,9,10
		//В нашем случае для удобста будем брать диапазон от последнего UID + 1
		//и до 2147483647.
		$uid_from = $last_uid + 1;
		$uid_to = 2147483647;		
		$range = "$uid_from:$uid_to";		
		$arr = imap_fetch_overview($mail,$range,FT_UID);		
		$message_uid = -1;
		//перебираем сообщения
		foreach($arr as $obj){
			//получаем UID сообщения
			$message_uid = $obj->uid;
			wr("add message $message_uid");
 
			//создаем запись в таблице messages,
			//тем самым поставив сообщение в очередь на загрузку
			$sql = "INSERT INTO messages(mailbox_id,uid,create_date,is_ready)
					VALUES($mailbox_id,$message_uid,now(),0)";
			mysql_query($sql) or die(mysql_error());
		}
 
		if($message_uid != - 1){
			wr("last message uid = $message_uid");
 
			//если появились новые сообщения, 
			//то сохраняем UID последнего сообщения
			$sql = "UPDATE mailboxes 
					SET last_message_uid = $message_uid
					WHERE id = $mailbox_id";
			mysql_query($sql) or die(mysql_error());
		}else{
			//нет новых сообщений
			wr("no new messages");
		}
	}
 
	mysql_query("COMMIT");//завершаем транзакцию
 
	//закрываем IMAP-поток
	imap_close($mail);
}
?>

Установка Apache, подключение PHP и MySQL

Первая статья в этом новом 2014 году 🙂
В очередной раз после переустановки Windows возникла необходимость в установке Apache с поддержкой PHP и MySQL.

Установка Apache
Скачаем последную версию Apache с официального сайта http://httpd.apache.org/download.cgi

Найдите и скачайте MSI-инсталлятор: httpd-2.0.65-win32-x86-no_ssl.msi (Скачать).
Если нужен Apache с OpenSQL, то выберите: httpd-2.0.65-win32-x86-openssl-0.9.8y.msi (Скачать)

Запустите установщик httpd-2.0.65-win32-x86-no_ssl.msi
В окне приветствия нажмите Next:
01 - welcome

Согласившись с лицензионным соглашением, выберите пункт «I accept the terms in the license agreement» и нажмите Next:
02 - agree

В следующем информационном окне просто нажмите Next:
03 - info

Далее следует заполнить информацию о вашем сервере. Можно заполнить как в данном примере.
Выберите первый рекомендуемый пункт и нажмите Next, это будет означать, что Apache будет установлен как сервис для всех пользователей и будет работать по 80-му порту:
04 - as service

Выберите обычную установку (Typical) и нажмите Next:
05 - typical

Измените директорию установки на диск C: и нажмите Next
06 - dest

Всё готово для установки, нажмите Install:
07 - install

После завершения процесса установки нажмите кнопку Finish:
08 - finish

Примечание: Процесс установки может завершиться некорректно из-за того, что 80 порт может быть занят другим приложением, и нужно будет выбрать другой порт. Например: 8421.
Откроем конфигурационный файл:
C:\Apache2\conf\httpd.conf
Заменим строку Listen 80 на Listen 8421.
Запустим установщик httpd-2.0.65-win32-x86-no_ssl.msi заново.
В окне приветствия нажмите Next.
В следующем окне выберите Repair и нажмите Next:
09 - repair
Всего готово к починке, нажав кнопку Install, Apache должен починиться.

Если установка прошла успешно, все равно рекомендую поменять 80-ый порт на другой, чтобы не было конфликтов ни сейчас, ни в будущем. Как было описано ранее нужно в файле C:\Apache2\conf\httpd.conf заменить строку Listen 80 на Listen 8421. Остается перезапустить службу Apache2 и наш веб-сервер доступен по 8421-му порту.
В браузере введите адрес: http://localhost:8421
Должна появиться стартовая страница Apache:
10 - ready

Подключение PHP
C официального сайта http://windows.php.net/download/ скачайте zip-архив с PHP или скачайте здесь.
Если у вас 64-битная Windows, то можете выбрать x64 (лучшая производительность) или x86.
Если у вас 32-битная Windows, то вам подойдет только x86.
Распакуйте zip-архив php-5.3.5-Win32-VC6-x86.zip в папку C:\Php

Откройте конфигурационный файл C:\Apache2\conf\httpd.conf
И в начало этого файла добавить следующие строки:

PHPIniDir "C:/Php/"  
LoadModule php5_module C:/Php/php5apache2.dll
AddType application/x-httpd-php phtml php 

В строке LoadModule php5_module C:/Php/php5apache2.dll вам нужно будет указать вашу версию библиотеки.
Будьте внимательны! Указывайте библиотеку, соответствующую вашей версии Apache. В нашем случае версия Apache 2.0, поэтому мы указали php5apache2.dll.
Для версии 2.2 — php5apache2_2.dll, для версия 2.4 — php5apache2_4.dll и т.д. Если укажите не ту версию, то Apache не запуститься, а в логах будет примерно такая ошибка:

Syntax error on line 2 of C:/Apache2/conf/httpd.conf

В httpd.conf найдите строку:

DirectoryIndex index.html index.html.var

Замените ее на:

DirectoryIndex index.php index.html index.html.var

В DirectoryIndex определяется порядок файлов по умолчанию.

Теперь приступим к настройке самого PHP.
Найстройки PHP храняться в файле C:\Php\php.ini, этого файла пока нет, но есть два других

  • php.ini-development (для разработки)
  • php.ini-production (для промышленного использования)

Нас устраивает php.ini-development, поэтому просто переименуем его в php.ini

Теперь необходимо в файле php.ini включить расширения mysql и mysqli.
Для этого найдите строки:

;extension=php_mysql.dll
;extension=php_mysqli.dll

Где строчки помеченные символом ; отключены
Для включения расширений php_mysql.dll и php_mysqli.dll нужно убрать ;

extension=php_mysql.dll
extension=php_mysqli.dll

Также найдите строку:

; extension_dir = "ext"

Замените её на следующую:

extension_dir = "c:/php/ext"

MySQLi является обновленной версией драйвера PHP MySQL, и дает различные улучшения в работе с базами данных.

Также поддержка сокращенного тега <? по умолчанию отключена, и вам нужно использовать полную версию тега <?php
Для это пропишите настройку так:

short_open_tag = On

Сохраните сделанные изменения и перегрузите Apache.