创建移动应用移动应用
This is the second article in a two part series in which I show you how to create a photo blog that is updated from your mobile device. Part 1 laid out the plan for building the application, set up the database requirements, and introduced you to some user contributed functions from the PHP manual which make it easy to retrieve messages from a POP3 mail server. In this installment I show you how to bring everything together so you can start photo-blogging on the go.
这是分两部分的系列文章的第二篇,其中我向您展示如何创建一个从移动设备更新的照片博客。 第1部分列出了构建应用程序的计划,设置数据库要求,并向您介绍了PHP手册中的一些用户贡献功能,这些功能使从POP3邮件服务器检索消息变得容易。 在本期中,我将向您展示如何将所有内容整合在一起,以便您可以随时随地开始写照片博客。
In Part 1 you saw how to retrieve a list of emails from the server. Of course, it would be unwise to publish them immediately in case your secret email address was discovered, which is why updates should be explicitly approved. Before proceeding, I want to set some more constants and connect to the database.
在第1部分中,您了解了如何从服务器检索电子邮件列表。 当然,万一发现您的秘密电子邮件地址,立即发布它们是不明智的,这就是为什么应该明确批准更新的原因。 在继续之前,我想设置一些常量并连接到数据库。
<?php // miscellaneous define("PUBLIC_EMAIL", "mobile@example.com"); 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);The PUBLIC_EMAIL constant is your mobile device’s email address to which approval messages will be sent. MAX_WIDTH is the maximum width allowed when the images are resized as thumbnails. The ROOT_PATH is an absolute path to a web-accessible location, and IMG_PATH is a writable folder in which your images will be stored. Note how IMG_PATH has no forward slash at the beginning; this is not a mistake as it is a relative path inside ROOT_PATH.
PUBLIC_EMAIL常数是您的移动设备的电子邮件地址,批准消息将发送到该电子邮件地址。 MAX_WIDTH是将图像调整为缩略图时允许的最大宽度。 ROOT_PATH是可通过Web访问的位置的绝对路径,而IMG_PATH是可存储图像的可写文件夹。 请注意, IMG_PATH开头如何没有正斜杠; 这不是错误,因为它是ROOT_PATH内部的相对路径。
While iterating the list of emails, the script checks that each message has a token associated with it. If a message doesn’t, the script creates a new token and sends off an email to ask for approval. Messages that have a token are checked to see whether or not they have been approved. See the following example:
在迭代电子邮件列表时,脚本将检查每个消息是否具有与之关联的令牌。 如果没有消息,脚本将创建一个新令牌并发送一封电子邮件以请求批准。 检查具有令牌的消息,以查看它们是否已被批准。 请参见以下示例:
<?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); } } }If $isValid has the value Y then the message has been approved. If $isValid has a value other than Y then the message has not been approved so you don’t need to go any further. If $isValid has no value then you need to create a token.
如果$isValid的值为Y则消息已被批准。 如果$isValid的值不是Y则该消息未被批准,因此您无需再进行任何操作。 如果$isValid没有值,则需要创建一个令牌。
The approve.php page referenced in the notification email simply changes the status of the token:
通知电子邮件中引用的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>"; } }Because the first script will be setup to run as a cron job every few minutes, the next time it executes it will see a valid flag because of the approval. If you ever receive a notification email which you did not submit, then you know the private receiving email address has somehow been compromised and you should probably change it. In any case, nothing will be published without approval.
由于第一个脚本将设置为每隔几分钟作为cron作业运行,因此下一次执行将由于得到批准而显示有效标志。 如果您收到未提交的通知电子邮件,则表明您的私人接收电子邮件地址已受到某种程度的破坏,您应该更改它。 在任何情况下,未经批准都不会发布任何内容。
Now that you have a token marked for approval, you need to extract the email contents, copy the attached images to the server and update the database. This code is a continuation of the previous script that runs under cron and picks up where $isValid indicates the message has been approved.
现在,您已将标记标记为要批准,您需要提取电子邮件内容,将附加的图像复制到服务器并更新数据库。 此代码是在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) { // ...In the example above, the date provided by the email client is no good for the database so it is converted to a unix timestamp. The body is a little tricky in that depending on which email client you use it may send multiple versions, so we attempt to weed out the plain text version and ignore the others.
在上面的示例中,电子邮件客户端提供的日期对数据库不利,因此将其转换为unix时间戳。 该主体有些棘手,因为它取决于您使用的电子邮件客户端可能会发送多个版本,因此我们尝试清除纯文本版本,而忽略其他版本。
To copy images, the full path of where they will be stored is defined first. Next, you grab the extension and make sure it is an allowed image format and then calculate the sha1() hash of the image to get a unique filename. Even though phones automatically give pictures unique filenames, there is always the chance you could change phones so you still want to give the images completely unique filenames before copying them to the server to make sure nothing gets overwritten. Thumbnails are given the same name with “_t” added to the end.
要复制图像,将首先定义其存储位置的完整路径。 接下来,获取扩展名并确保它是允许的图像格式,然后计算图像的sha1()哈希值以获得唯一的文件名。 即使电话自动为图片提供唯一的文件名,也总是有机会更改电话,因此您仍然希望为图片提供完全唯一的文件名,然后再将其复制到服务器以确保不会覆盖任何内容。 缩略图的名称相同,结尾处添加了“ _t ”。
While Imagick is relatively fast and efficient with its memory usage, you may still run into problems if you attempt to upload more than a few very large images. If you do run into problems then you’ll want to do some testing to make sure your script isn’t running out of memory, timing out, or that the cron job isn’t trying to run the script again while a previous run is still processing.
尽管Imagick的内存使用速度相对较快且效率很高,但是如果您尝试上传多个非常大的图像,则可能仍然会遇到问题。 如果确实遇到问题,则需要进行一些测试,以确保您的脚本没有耗尽内存,超时或在上次运行时cron作业没有尝试再次运行该脚本。仍在处理中。
After the blog content has been extracted from the message body and the images are copied, you update the database and delete the token since it is no longer needed. You also need to re-index your tokens at this point because once an email is deleted from the inbox, any remaining messages will be automatically re-numbered. For example, if you have the following messages in your inbox:
从消息正文中提取博客内容并复制图像之后,您将更新数据库并删除令牌,因为不再需要它。 此时,您还需要重新为令牌编制索引,因为一旦从收件箱中删除了一封电子邮件,所有剩余的邮件都会自动重新编号。 例如,如果您的收件箱中有以下消息:
message_id subject =================================== 1 hello world 2 foo 3 barIf you process and delete message 1, the remaining messages will be re-numbered as follows:
如果您处理并删除消息1,其余消息将按以下方式重新编号:
message_id subject =================================== 1 foo 2 barSince tokens are stored with a reference to a specific message id, if you don’t re-index the tokens to accommodate for this behavior then you’ll end up with a complete mess.
由于令牌是通过对特定消息ID的引用存储的,因此,如果不重新索引令牌以适应这种行为,那么最终将导致混乱。
At the end of this example you mark the email for deletion and call the break command to stop looping through $msgList. Not only does this ensure that only one blog will be updated per execution which can help save memory, but since the token ids have been updated the remaining entries in this cycle wouldn’t be valid anyway.
在本示例的最后,您将电子邮件标记为删除,并调用break命令以停止循环通过$msgList 。 这不仅确保每次执行仅更新一个博客,这可以帮助节省内存,而且由于令牌ID已更新,因此该循环中的其余条目将始终无效。
When the script terminates, the POP3 class’ destructor will be called and the processed email will be deleted.
脚本终止后,将调用POP3类的析构函数,并删除已处理的电子邮件。
When it comes to displaying the content, the options are endless. You could further improve the blog to add BBCode, comments, pagination, and so on and so forth. Whatever your needs may be, the following example should help get you started:
当显示内容时,选项无穷无尽。 您可以进一步改善博客,以添加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>"; }You now know how to write your own photo blog application from the ground up to post images and text right from your mobile device. Source code for this project can be downloaded from GitHub.
现在,您知道如何从头开始编写自己的照片博客应用程序,以直接在移动设备上发布图像和文本。 该项目的源代码可以从GitHub下载。
I hope this article was as helpful to you as it was fun for me writing it. It’s side projects like these that can really help motivate us to keep blogging even when it feels like a chore. As an added bonus, it’s entertaining content for your visitors; I mean, who doesn’t like looking at pictures?
我希望本文对您有帮助,对我写这篇文章也很有趣。 像这样的附带项目,确实可以帮助我们激发写博客的乐趣,即使感觉很琐碎。 另外,它为您的访客提供娱乐性的内容; 我的意思是,谁不喜欢看图片?
Image via Angela Waye / Shutterstock
图片来自Angela Waye / Shutterstock
翻译自: https://www.sitepoint.com/creating-a-mobile-photo-blog-part-2/
创建移动应用移动应用