Это вторая статья из двух частей, в которой я покажу вам, как создать фотоблог, который обновляется с вашего мобильного устройства. В первой части был изложен план построения приложения, настроены требования к базе данных, а также представлены некоторые пользовательские функции из руководства по PHP, которые упрощают получение сообщений с почтового сервера POP3. В этой статье я покажу вам, как собрать все вместе, чтобы вы могли начать фотографировать на ходу.
Запрашиваемая одобрение
В первой части вы увидели, как получить список электронных писем с сервера. Конечно, было бы неразумно публиковать их немедленно в случае обнаружения вашего секретного адреса электронной почты, поэтому обновления должны быть явно одобрены. Прежде чем продолжить, я хочу установить еще несколько констант и подключиться к базе данных.
<?php // miscellaneous define("PUBLIC_EMAIL", "[email protected]"); define("MAX_WIDTH", 240); define("ROOT_PATH", "/var/www/"); define("IMG_PATH", "photoblog/"); // database connection define("DB_HOST", "localhost"); define("DB_USER", "root"); define("DB_PASSWORD", "********"); define("DB_NAME", "test"); // connect to database $db = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASSWORD);
Константа PUBLIC_EMAIL
— это адрес электронной почты вашего мобильного устройства, на который будут отправляться подтверждающие сообщения. MAX_WIDTH
— это максимальная ширина, разрешенная при изменении размеров изображений в виде миниатюр. ROOT_PATH
— это абсолютный путь к веб-доступному местоположению, а IMG_PATH
— IMG_PATH
для записи папка, в которой будут храниться ваши изображения. Обратите внимание, что IMG_PATH
не имеет косой черты в начале; это не ошибка, поскольку это относительный путь внутри ROOT_PATH
.
Во время итерации списка электронных писем скрипт проверяет, связан ли с каждым сообщением токен. Если сообщения нет, сценарий создает новый токен и отправляет электронное письмо с просьбой одобрить. Сообщения с токеном проверяются, чтобы увидеть, были ли они утверждены. Смотрите следующий пример:
<?php require_once "POP3.php"; // prepared SQL statements $sqlSelectPending = "SELECT is_valid FROM pending WHERE message_id = :msgno"; $stmSelectPending = $db->prepare($sqlSelectPending); $sqlInsertPending = "INSERT INTO pending (message_id, token) VALUES (:msgno, :token)"; $stmInsertPending = $db->prepare($sqlInsertPending); // retrieve messages $pop3 = new POP3(EMAIL_HOST, EMAIL_USER, EMAIL_PASSWORD); $msgList = $pop3->listMessages(); if (!empty($msgList)) { foreach ($msgList as $value) { // see if a token exists $stmSelectPending->execute(array(":msgno" => $value["msgno"])); $isValid = $stmSelectPending->fetchColumn(); // message has been approved if ($isValid == "Y") { // ... } // message has no token elseif ($isValid === false) { // create a unique token $token = md5(uniqid(mt_rand(), true)); $stmInsertPending->execute(array(":msgno" => $value["msgno"], ":token" => $token)); // send email for approval $title = htmlentities($value["subject"], ENT_QUOTES); $subject = "Pending Post Notification: " . $title; $message = '<a href="http://www.example.com/approve.php?token=' . $token . '">Click Here To Approve</a>'; mail(PUBLIC_EMAIL, $subject, $message); } } }
Если $isValid
имеет значение Y
сообщение было одобрено. Если $isValid
имеет значение, отличное от Y
сообщение не было утверждено, поэтому вам не нужно идти дальше. Если $isValid
не имеет значения, вам нужно создать токен.
Страница approve.php
ссылается электронное письмо с уведомлением, просто меняет статус токена:
<?php // connect to database // ... // receive incoming token $token = isset($_GET["token"]) && ctype_alnum($_GET["token"]) ? $_GET["token"] : null; if (!empty($token)) { // verify token $sql = "SELECT message_id FROM pending WHERE token = :token AND is_valid = 'N'"; $stm = $db->prepare($sql); $stm->execute(array(":token" => $token)); $pendID = $stm->fetchColumn(); $stm->closeCursor(); if (!empty($pendID)) { // set the entry to be published $sql = "UPDATE pending SET is_valid = 'Y' WHERE message_id = :pend_id"; $stm = $db->prepare($sql); $stm->execute(array(":pend_id" => $pendID)); echo "<p>Publishing...</p>"; } else { echo "<p>Invalid token.</p>"; } }
-<?php // connect to database // ... // receive incoming token $token = isset($_GET["token"]) && ctype_alnum($_GET["token"]) ? $_GET["token"] : null; if (!empty($token)) { // verify token $sql = "SELECT message_id FROM pending WHERE token = :token AND is_valid = 'N'"; $stm = $db->prepare($sql); $stm->execute(array(":token" => $token)); $pendID = $stm->fetchColumn(); $stm->closeCursor(); if (!empty($pendID)) { // set the entry to be published $sql = "UPDATE pending SET is_valid = 'Y' WHERE message_id = :pend_id"; $stm = $db->prepare($sql); $stm->execute(array(":pend_id" => $pendID)); echo "<p>Publishing...</p>"; } else { echo "<p>Invalid token.</p>"; } }
Поскольку первый сценарий будет настроен для запуска в качестве задания cron каждые несколько минут, при следующем запуске он увидит действительный флаг из-за утверждения. Если вы когда-либо получали уведомление по электронной почте, которое вы не отправляли, то вы знаете, что частный адрес электронной почты был каким-то образом скомпрометирован, и вам, вероятно, следует его изменить. В любом случае, ничего не будет опубликовано без одобрения.
Издательские блоги
Теперь, когда у вас есть токен, помеченный для утверждения, вам нужно извлечь содержимое электронной почты, скопировать прикрепленные изображения на сервер и обновить базу данных. Этот код является продолжением предыдущего скрипта, который запускается в cron и берет там, где $isValid
указывает, что сообщение было одобрено.
<?php // prepared SQL statements // ... $sqlUpdatePending = "UPDATE pending SET message_id = message_id - 1 WHERE message_id > :msgno"; $stmUpdatePending = $db->prepare($sqlUpdatePending); $sqlDeletePending = "DELETE FROM pending WHERE message_id = :msgno"; $stmDeletePending = $db->prepare($sqlDeletePending); $sqlInsertPost = "INSERT INTO blog_posts (title, body, create_ts) VALUES (:title, :body, FROM_UNIXTIME(:timestamp))"; $stmInsertPost = $db->prepare($sqlInsertPost); $sqlInsertImage = "INSERT INTO images (post_id, image_path) VALUES (:post_id, :img_path)"; $stmInsertImage = $db->prepare($sqlInsertImage); // ... // message has been approved if ($isValid == "Y") { // get message contents $msg = $pop3->mimeToArray($value["msgno"], true); // convert date to timestamp $timestamp = strtotime($value["date"]); if ($timestamp === false) { $timestamp = null; } $title = $value["subject"]; if(sizeof($msg) > 1) { $body = (isset($msg["1.1"])) ? $msg["1.1"]["data"] : $msg[1]["data"]; } else { $body = $pop3->fetchBody($value["msgno"]); } // copy images to server $files = array(); foreach ($msg as $parts) { if (isset($parts["filename"])) { $dir = ROOT_PATH . IMG_PATH; $ext = strtolower(pathinfo($parts["filename"], PATHINFO_EXTENSION)); // only accept jpg or png if (in_array($ext, array("jpg","png"))) { // give the file a unique name $hash = sha1($parts["data"]); $file = $hash . "." . $ext; $thumb = $hash . "_t." . $ext; if (!file_exists($dir . $file)) { // copy image and make thumbnails $img = new Imagick(); $img->readimageblob($parts["data"]); $img->writeImage($dir . $file); $img->thumbnailImage(MAX_WIDTH, 0); $img->writeImage($dir . $thumb); $img->clear(); $img->destroy(); } $files[] = IMG_PATH . $file; } } } // update database if (isset($timestamp, $title, $body)) { // insert post $stmInsertPost->execute(array(":title" => $title, ":body" => $body, ":timestamp" => $timestamp)); $postID = $db->lastInsertId(); // insert images $stmInsertImage->bindParam(":post_id", $postID); $stmInsertImage->bindParam(":img_path", $path); foreach($files as $path) { $stmInsertImage->execute(); } // delete token $stmDeletePending->execute(array(":msgno" => $value["msgno"])); // update existing tokens $stmUpdatePending->execute(array(":msgno" => $value["msgno"])); } // mark email for deletion $pop3->deleteMessage($value["msgno"]); break; } // message has no approval token elseif ($isValid === false) { // ...
В приведенном выше примере дата, предоставленная почтовым клиентом, не подходит для базы данных, поэтому она преобразуется в метку времени Unix. Тело немного сложное в том смысле, что в зависимости от того, какой почтовый клиент вы используете, оно может отправлять несколько версий, поэтому мы пытаемся отсеять текстовую версию и игнорировать другие.
Для копирования изображений сначала указывается полный путь их хранения. Затем вы берете расширение и убедитесь, что это допустимый формат изображения, а затем вычисляете хэш sha1()
изображения, чтобы получить уникальное имя файла. Даже если телефоны автоматически присваивают изображениям уникальные имена файлов, всегда есть возможность сменить телефоны, поэтому вы все же хотите дать изображениям совершенно уникальные имена файлов перед тем, как копировать их на сервер, чтобы ничего не перезаписывалось. Миниатюры имеют одно и то же имя с добавлением « _t
» в конце.
Хотя Imagick относительно быстр и эффективен с использованием памяти, вы все равно можете столкнуться с проблемами, если попытаетесь загрузить более нескольких очень больших изображений. Если вы столкнетесь с проблемами, то вам нужно провести некоторое тестирование, чтобы убедиться, что в вашем скрипте не хватает памяти, не истекло время ожидания или что задание cron не пытается запустить скрипт снова, пока выполняется предыдущий запуск. все еще обрабатывает.
После извлечения содержимого блога из тела сообщения и копирования изображений вы обновляете базу данных и удаляете токен, поскольку он больше не нужен. На этом этапе вам также необходимо переиндексировать свои токены, поскольку после удаления электронного письма из папки входящих сообщений все оставшиеся сообщения будут автоматически перенумерованы. Например, если у вас есть следующие сообщения в папке «Входящие»:
тема сообщения =================================== 1 привет мир 2 фу 3 бара
Если вы обработаете и удалите сообщение 1, остальные сообщения будут перенумерованы следующим образом:
тема сообщения =================================== 1 фу 2 бара
Поскольку токены хранятся со ссылкой на конкретный идентификатор сообщения, если вы не переиндексируете токены, чтобы приспособиться к такому поведению, вы получите полный беспорядок.
В конце этого примера вы помечаете письмо для удаления и вызываете команду прерывания, чтобы прекратить циклический просмотр $msgList
. Это не только гарантирует, что только один блог будет обновляться за выполнение, что может помочь сэкономить память, но так как идентификаторы токенов были обновлены, оставшиеся записи в этом цикле в любом случае не будут действительными.
Когда сценарий завершается, POP3
деструктор класса POP3
и обработанное письмо будет удалено.
Отображение блога
Когда дело доходит до отображения контента, варианты безграничны. Вы можете улучшить блог, добавив BBCode, комментарии, нумерацию страниц и т. Д. И т. Д. Какими бы ни были ваши потребности, следующий пример должен помочь вам начать:
<?php // connect to database // ... // prepared SQL statements $sqlSelectImages = "SELECT * FROM images WHERE post_id = :post_id"; $stmSelectImages = $db->prepare($sqlSelectImages); // output each blog post $sqlSelectPosts = "SELECT * FROM blog_posts ORDER BY create_ts DESC"; $result = $db->query($sqlSelectPosts); while ($row = $result->fetch(PDO::FETCH_ASSOC)) { $stmSelectImages->execute(array(":post_id" => $row["post_id"])); $images = $stmSelectImages->fetchAll(); echo "<div>"; echo "<h2>" . htmlspecialchars($row["title"]) . "</h2>"; echo "<p>" . htmlspecialchars($row["body"]) . "</p>"; if (!empty($images)) { // output thumbnail and link for each image foreach ($images as $img) { $ext = "." . pathinfo($img["image_path"], PATHINFO_EXTENSION); $thumb = str_replace($ext, "_t" . $ext, $img["image_path"]); echo '<a href="' . $img["image_path"] . '">'; echo '<img src="' . $thumb . '" alt="' . basename($img["image_path"]) . '">'; echo "</a>"; } } echo "</div>"; }
Резюме
Теперь вы знаете, как написать собственное приложение для создания фотоблога с нуля, чтобы публиковать изображения и текст прямо с вашего мобильного устройства. Исходный код этого проекта можно скачать с GitHub.
Я надеюсь, что эта статья была для вас такой же полезной, как и для меня интересной. Именно такие побочные проекты действительно могут мотивировать нас продолжать вести блог, даже когда это кажется рутиной. В качестве дополнительного бонуса, это развлекательный контент для ваших посетителей; Я имею в виду, кому не нравится смотреть на картинки?
Изображение через Анжелу Уэй / Shutterstock